mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-18 18:11:02 -07:00
feat: Implement message bus and policy engine (#11523)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
This commit is contained in:
@@ -14,16 +14,70 @@ import {
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
describe('createPolicyEngineConfig', () => {
|
||||
it('should return ASK_USER for all tools by default', () => {
|
||||
it('should return ASK_USER for write tools and ALLOW for read-only tools by default', () => {
|
||||
const settings: Settings = {};
|
||||
const config = createPolicyEngineConfig(settings, ApprovalMode.DEFAULT);
|
||||
expect(config.defaultDecision).toBe(PolicyDecision.ASK_USER);
|
||||
// The order of the rules is not guaranteed, so we sort them by tool name.
|
||||
config.rules?.sort((a, b) =>
|
||||
(a.toolName ?? '').localeCompare(b.toolName ?? ''),
|
||||
);
|
||||
expect(config.rules).toEqual([
|
||||
{ toolName: 'replace', decision: 'ask_user', priority: 10 },
|
||||
{ toolName: 'save_memory', decision: 'ask_user', priority: 10 },
|
||||
{ toolName: 'run_shell_command', decision: 'ask_user', priority: 10 },
|
||||
{ toolName: 'write_file', decision: 'ask_user', priority: 10 },
|
||||
{ toolName: WEB_FETCH_TOOL_NAME, decision: 'ask_user', priority: 10 },
|
||||
{
|
||||
toolName: 'glob',
|
||||
decision: PolicyDecision.ALLOW,
|
||||
priority: 50,
|
||||
},
|
||||
{
|
||||
toolName: 'google_web_search',
|
||||
decision: PolicyDecision.ALLOW,
|
||||
priority: 50,
|
||||
},
|
||||
{
|
||||
toolName: 'list_directory',
|
||||
decision: PolicyDecision.ALLOW,
|
||||
priority: 50,
|
||||
},
|
||||
{
|
||||
toolName: 'read_file',
|
||||
decision: PolicyDecision.ALLOW,
|
||||
priority: 50,
|
||||
},
|
||||
{
|
||||
toolName: 'read_many_files',
|
||||
decision: PolicyDecision.ALLOW,
|
||||
priority: 50,
|
||||
},
|
||||
{
|
||||
toolName: 'replace',
|
||||
decision: PolicyDecision.ASK_USER,
|
||||
priority: 10,
|
||||
},
|
||||
{
|
||||
toolName: 'run_shell_command',
|
||||
decision: PolicyDecision.ASK_USER,
|
||||
priority: 10,
|
||||
},
|
||||
{
|
||||
toolName: 'save_memory',
|
||||
decision: PolicyDecision.ASK_USER,
|
||||
priority: 10,
|
||||
},
|
||||
{
|
||||
toolName: 'search_file_content',
|
||||
decision: PolicyDecision.ALLOW,
|
||||
priority: 50,
|
||||
},
|
||||
{
|
||||
toolName: 'web_fetch',
|
||||
decision: PolicyDecision.ASK_USER,
|
||||
priority: 10,
|
||||
},
|
||||
{
|
||||
toolName: 'write_file',
|
||||
decision: PolicyDecision.ASK_USER,
|
||||
priority: 10,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -159,18 +213,6 @@ describe('createPolicyEngineConfig', () => {
|
||||
expect(excludedRule?.priority).toBe(195);
|
||||
});
|
||||
|
||||
it('should allow read-only tools if autoAccept is true', () => {
|
||||
const settings: Settings = {
|
||||
tools: { autoAccept: true },
|
||||
};
|
||||
const config = createPolicyEngineConfig(settings, ApprovalMode.DEFAULT);
|
||||
const rule = config.rules?.find(
|
||||
(r) => r.toolName === 'glob' && r.decision === PolicyDecision.ALLOW,
|
||||
);
|
||||
expect(rule).toBeDefined();
|
||||
expect(rule?.priority).toBe(50);
|
||||
});
|
||||
|
||||
it('should allow all tools in YOLO mode', () => {
|
||||
const settings: Settings = {};
|
||||
const config = createPolicyEngineConfig(settings, ApprovalMode.YOLO);
|
||||
@@ -419,29 +461,6 @@ describe('createPolicyEngineConfig', () => {
|
||||
// Exclude (195) should win over trust (90) when evaluated
|
||||
});
|
||||
|
||||
it('should create all read-only tool rules when autoAccept is enabled', () => {
|
||||
const settings: Settings = {
|
||||
tools: { autoAccept: true },
|
||||
};
|
||||
const config = createPolicyEngineConfig(settings, ApprovalMode.DEFAULT);
|
||||
|
||||
// All read-only tools should have allow rules
|
||||
const readOnlyTools = [
|
||||
'glob',
|
||||
'search_file_content',
|
||||
'list_directory',
|
||||
'read_file',
|
||||
'read_many_files',
|
||||
];
|
||||
for (const tool of readOnlyTools) {
|
||||
const rule = config.rules?.find(
|
||||
(r) => r.toolName === tool && r.decision === PolicyDecision.ALLOW,
|
||||
);
|
||||
expect(rule).toBeDefined();
|
||||
expect(rule?.priority).toBe(50);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle all approval modes correctly', () => {
|
||||
const settings: Settings = {};
|
||||
|
||||
|
||||
@@ -22,8 +22,12 @@ import {
|
||||
EDIT_TOOL_NAME,
|
||||
MEMORY_TOOL_NAME,
|
||||
WEB_SEARCH_TOOL_NAME,
|
||||
type PolicyEngine,
|
||||
type MessageBus,
|
||||
MessageBusType,
|
||||
type UpdatePolicy,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { Settings } from './settings.js';
|
||||
import { type Settings } from './settings.js';
|
||||
|
||||
// READ_ONLY_TOOLS is a list of built-in tools that do not modify the user's
|
||||
// files or system state.
|
||||
@@ -69,6 +73,7 @@ export function createPolicyEngineConfig(
|
||||
// 90: MCP servers with trust=true
|
||||
// 100: Explicitly allowed individual tools
|
||||
// 195: Explicitly excluded MCP servers
|
||||
// 199: Tools that the user has selected as "Always Allow" in the interactive UI.
|
||||
// 200: Explicitly excluded individual tools (highest priority)
|
||||
|
||||
// MCP servers that are explicitly allowed in settings.mcp.allowed
|
||||
@@ -137,16 +142,14 @@ export function createPolicyEngineConfig(
|
||||
}
|
||||
}
|
||||
|
||||
// If auto-accept is enabled, allow all read-only tools.
|
||||
// Allow all read-only tools.
|
||||
// Priority: 50
|
||||
if (settings.tools?.autoAccept) {
|
||||
for (const tool of READ_ONLY_TOOLS) {
|
||||
rules.push({
|
||||
toolName: tool,
|
||||
decision: PolicyDecision.ALLOW,
|
||||
priority: 50,
|
||||
});
|
||||
}
|
||||
for (const tool of READ_ONLY_TOOLS) {
|
||||
rules.push({
|
||||
toolName: tool,
|
||||
decision: PolicyDecision.ALLOW,
|
||||
priority: 50,
|
||||
});
|
||||
}
|
||||
|
||||
// Only add write tool rules if not in YOLO mode
|
||||
@@ -179,3 +182,21 @@ export function createPolicyEngineConfig(
|
||||
defaultDecision: PolicyDecision.ASK_USER,
|
||||
};
|
||||
}
|
||||
|
||||
export function createPolicyUpdater(
|
||||
policyEngine: PolicyEngine,
|
||||
messageBus: MessageBus,
|
||||
) {
|
||||
messageBus.subscribe(
|
||||
MessageBusType.UPDATE_POLICY,
|
||||
(message: UpdatePolicy) => {
|
||||
const toolName = message.toolName;
|
||||
|
||||
policyEngine.addRule({
|
||||
toolName,
|
||||
decision: PolicyDecision.ALLOW,
|
||||
priority: 199, // High priority, but lower than explicit DENY (200)
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -170,6 +170,10 @@ describe('gemini.tsx main function', () => {
|
||||
getScreenReader: () => false,
|
||||
getGeminiMdFileCount: () => 0,
|
||||
getProjectRoot: () => '/',
|
||||
getPolicyEngine: vi.fn(),
|
||||
getMessageBus: () => ({
|
||||
subscribe: vi.fn(),
|
||||
}),
|
||||
} as unknown as Config;
|
||||
});
|
||||
vi.mocked(loadSettings).mockReturnValue({
|
||||
@@ -301,6 +305,10 @@ describe('gemini.tsx main function kitty protocol', () => {
|
||||
getExperimentalZedIntegration: () => false,
|
||||
getScreenReader: () => false,
|
||||
getGeminiMdFileCount: () => 0,
|
||||
getPolicyEngine: vi.fn(),
|
||||
getMessageBus: () => ({
|
||||
subscribe: vi.fn(),
|
||||
}),
|
||||
} as unknown as Config);
|
||||
vi.mocked(loadSettings).mockReturnValue({
|
||||
errors: [],
|
||||
|
||||
@@ -67,6 +67,7 @@ import {
|
||||
relaunchOnExitCode,
|
||||
} from './utils/relaunch.js';
|
||||
import { loadSandboxConfig } from './config/sandboxConfig.js';
|
||||
import { createPolicyUpdater } from './config/policy.js';
|
||||
import { ExtensionEnablementManager } from './config/extensions/extensionEnablement.js';
|
||||
|
||||
export function validateDnsResolutionOrder(
|
||||
@@ -370,6 +371,10 @@ export async function main() {
|
||||
argv,
|
||||
);
|
||||
|
||||
const policyEngine = config.getPolicyEngine();
|
||||
const messageBus = config.getMessageBus();
|
||||
createPolicyUpdater(policyEngine, messageBus);
|
||||
|
||||
// Cleanup sessions after config initialization
|
||||
await cleanupExpiredSessions(config, settings.merged);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user