Skip to content

Commit 913067e

Browse files
abhipatel12gemini-cli-robot
authored andcommitted
chore(config): disable agents by default (#23546)
# Conflicts: # integration-tests/browser-policy.test.ts # packages/a2a-server/src/config/config.test.ts # packages/a2a-server/src/config/config.ts
1 parent 63c6560 commit 913067e

8 files changed

Lines changed: 252 additions & 6 deletions

File tree

docs/reference/configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1159,7 +1159,7 @@ their corresponding top-level category object in your `settings.json` file.
11591159

11601160
- **`experimental.enableAgents`** (boolean):
11611161
- **Description:** Enable local and remote subagents.
1162-
- **Default:** `true`
1162+
- **Default:** `false`
11631163
- **Requires restart:** Yes
11641164

11651165
- **`experimental.extensionManagement`** (boolean):
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
/**
2+
* @license
3+
* Copyright 2026 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
8+
import { TestRig, poll } from './test-helper.js';
9+
import { dirname, join } from 'node:path';
10+
import { fileURLToPath } from 'node:url';
11+
import { execSync } from 'node:child_process';
12+
import { existsSync, writeFileSync, readFileSync, mkdirSync } from 'node:fs';
13+
import stripAnsi from 'strip-ansi';
14+
15+
const __filename = fileURLToPath(import.meta.url);
16+
const __dirname = dirname(__filename);
17+
18+
const chromeAvailable = (() => {
19+
try {
20+
if (process.platform === 'darwin') {
21+
execSync(
22+
'test -d "/Applications/Google Chrome.app" || test -d "/Applications/Chromium.app"',
23+
{
24+
stdio: 'ignore',
25+
},
26+
);
27+
} else if (process.platform === 'linux') {
28+
execSync(
29+
'which google-chrome || which chromium-browser || which chromium',
30+
{ stdio: 'ignore' },
31+
);
32+
} else if (process.platform === 'win32') {
33+
const chromePaths = [
34+
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
35+
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
36+
`${process.env['LOCALAPPDATA'] ?? ''}\\Google\\Chrome\\Application\\chrome.exe`,
37+
];
38+
const found = chromePaths.some((p) => existsSync(p));
39+
if (!found) {
40+
execSync('where chrome || where chromium', { stdio: 'ignore' });
41+
}
42+
} else {
43+
return false;
44+
}
45+
return true;
46+
} catch {
47+
return false;
48+
}
49+
})();
50+
51+
describe.skipIf(!chromeAvailable)('browser-policy', () => {
52+
let rig: TestRig;
53+
54+
beforeEach(() => {
55+
rig = new TestRig();
56+
});
57+
58+
afterEach(async () => {
59+
await rig.cleanup();
60+
});
61+
62+
it('should skip confirmation when "Allow all server tools for this session" is chosen', async () => {
63+
rig.setup('browser-policy-skip-confirmation', {
64+
fakeResponsesPath: join(__dirname, 'browser-policy.responses'),
65+
settings: {
66+
experimental: {
67+
enableAgents: true,
68+
},
69+
agents: {
70+
overrides: {
71+
browser_agent: {
72+
enabled: true,
73+
},
74+
},
75+
browser: {
76+
headless: true,
77+
sessionMode: 'isolated',
78+
allowedDomains: ['example.com'],
79+
},
80+
},
81+
},
82+
});
83+
84+
// Manually trust the folder to avoid the dialog and enable option 3
85+
const geminiDir = join(rig.homeDir!, '.gemini');
86+
mkdirSync(geminiDir, { recursive: true });
87+
88+
// Write to trustedFolders.json
89+
const trustedFoldersPath = join(geminiDir, 'trustedFolders.json');
90+
const trustedFolders = {
91+
[rig.testDir!]: 'TRUST_FOLDER',
92+
};
93+
writeFileSync(trustedFoldersPath, JSON.stringify(trustedFolders, null, 2));
94+
95+
// Force confirmation for browser agent.
96+
// NOTE: We don't force confirm browser tools here because "Allow all server tools"
97+
// adds a rule with ALWAYS_ALLOW_PRIORITY (3.9x) which would be overshadowed by
98+
// a rule in the user tier (4.x) like the one from this TOML.
99+
// By removing the explicit mcp rule, the first MCP tool will still prompt
100+
// due to default approvalMode = 'default', and then "Allow all" will correctly
101+
// bypass subsequent tools.
102+
const policyFile = join(rig.testDir!, 'force-confirm.toml');
103+
writeFileSync(
104+
policyFile,
105+
`
106+
[[rule]]
107+
name = "Force confirm browser_agent"
108+
toolName = "browser_agent"
109+
decision = "ask_user"
110+
priority = 200
111+
`,
112+
);
113+
114+
// Update settings.json in both project and home directories to point to the policy file
115+
for (const baseDir of [rig.testDir!, rig.homeDir!]) {
116+
const settingsPath = join(baseDir, '.gemini', 'settings.json');
117+
if (existsSync(settingsPath)) {
118+
const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
119+
settings.policyPaths = [policyFile];
120+
// Ensure folder trust is enabled
121+
settings.security = settings.security || {};
122+
settings.security.folderTrust = settings.security.folderTrust || {};
123+
settings.security.folderTrust.enabled = true;
124+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
125+
}
126+
}
127+
128+
const run = await rig.runInteractive({
129+
approvalMode: 'default',
130+
env: {
131+
GEMINI_CLI_INTEGRATION_TEST: 'true',
132+
},
133+
});
134+
135+
await run.sendKeys(
136+
'Open https://example.com and check if there is a heading\r',
137+
);
138+
await run.sendKeys('\r');
139+
140+
// Handle confirmations.
141+
// 1. Initial browser_agent delegation (likely only 3 options, so use option 1: Allow once)
142+
await poll(
143+
() => stripAnsi(run.output).toLowerCase().includes('action required'),
144+
60000,
145+
1000,
146+
);
147+
await run.sendKeys('1\r');
148+
await new Promise((r) => setTimeout(r, 2000));
149+
150+
// Handle privacy notice
151+
await poll(
152+
() => stripAnsi(run.output).toLowerCase().includes('privacy notice'),
153+
5000,
154+
100,
155+
);
156+
await run.sendKeys('1\r');
157+
await new Promise((r) => setTimeout(r, 5000));
158+
159+
// new_page (MCP tool, should have 4 options, use option 3: Allow all server tools)
160+
await poll(
161+
() => {
162+
const stripped = stripAnsi(run.output).toLowerCase();
163+
return (
164+
stripped.includes('new_page') &&
165+
stripped.includes('allow all server tools for this session')
166+
);
167+
},
168+
60000,
169+
1000,
170+
);
171+
172+
// Select "Allow all server tools for this session" (option 3)
173+
await run.sendKeys('3\r');
174+
await new Promise((r) => setTimeout(r, 30000));
175+
176+
const output = stripAnsi(run.output).toLowerCase();
177+
178+
expect(output).toContain('browser_agent');
179+
expect(output).toContain('completed successfully');
180+
});
181+
182+
it('should show the visible warning when browser agent starts in existing session mode', async () => {
183+
rig.setup('browser-session-warning', {
184+
fakeResponsesPath: join(__dirname, 'browser-agent.cleanup.responses'),
185+
settings: {
186+
experimental: {
187+
enableAgents: true,
188+
},
189+
general: {
190+
enableAutoUpdateNotification: false,
191+
},
192+
agents: {
193+
overrides: {
194+
browser_agent: {
195+
enabled: true,
196+
},
197+
},
198+
browser: {
199+
sessionMode: 'existing',
200+
headless: true,
201+
},
202+
},
203+
},
204+
});
205+
206+
const stdout = await rig.runCommand(['Open https://example.com'], {
207+
env: {
208+
GEMINI_API_KEY: 'fake-key',
209+
GEMINI_TELEMETRY_DISABLED: 'true',
210+
DEV: 'true',
211+
},
212+
});
213+
214+
expect(stdout).toContain('saved logins will be visible');
215+
});
216+
});

packages/a2a-server/src/config/config.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,32 @@ describe('loadConfig', () => {
325325
);
326326
});
327327

328+
<<<<<<< HEAD
329+
=======
330+
it('should pass enableAgents to Config constructor', async () => {
331+
const settings: Settings = {
332+
experimental: {
333+
enableAgents: false,
334+
},
335+
};
336+
await loadConfig(settings, mockExtensionLoader, taskId);
337+
expect(Config).toHaveBeenCalledWith(
338+
expect.objectContaining({
339+
enableAgents: false,
340+
}),
341+
);
342+
});
343+
344+
it('should default enableAgents to false when not provided', async () => {
345+
await loadConfig(mockSettings, mockExtensionLoader, taskId);
346+
expect(Config).toHaveBeenCalledWith(
347+
expect.objectContaining({
348+
enableAgents: false,
349+
}),
350+
);
351+
});
352+
353+
>>>>>>> b2d6dc4e3 (chore(config): disable agents by default (#23546))
328354
describe('interactivity', () => {
329355
it('should set interactive true when not headless', async () => {
330356
vi.mocked(isHeadlessMode).mockReturnValue(false);

packages/a2a-server/src/config/config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ export async function loadConfig(
110110
interactive: !isHeadlessMode(),
111111
enableInteractiveShell: !isHeadlessMode(),
112112
ptyInfo: 'auto',
113+
<<<<<<< HEAD
114+
=======
115+
enableAgents: settings.experimental?.enableAgents ?? false,
116+
>>>>>>> b2d6dc4e3 (chore(config): disable agents by default (#23546))
113117
};
114118

115119
const fileService = new FileDiscoveryService(workspaceDir, {

packages/cli/src/config/settingsSchema.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ describe('SettingsSchema', () => {
400400
expect(setting).toBeDefined();
401401
expect(setting.type).toBe('boolean');
402402
expect(setting.category).toBe('Experimental');
403-
expect(setting.default).toBe(true);
403+
expect(setting.default).toBe(false);
404404
expect(setting.requiresRestart).toBe(true);
405405
expect(setting.showInDialog).toBe(false);
406406
expect(setting.description).toBe('Enable local and remote subagents.');

packages/cli/src/config/settingsSchema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1838,7 +1838,7 @@ const SETTINGS_SCHEMA = {
18381838
label: 'Enable Agents',
18391839
category: 'Experimental',
18401840
requiresRestart: true,
1841-
default: true,
1841+
default: false,
18421842
description: 'Enable local and remote subagents.',
18431843
showInDialog: false,
18441844
},

packages/core/src/config/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -951,7 +951,7 @@ export class Config implements McpContext, AgentLoopContext {
951951
this.model = params.model;
952952
this.disableLoopDetection = params.disableLoopDetection ?? false;
953953
this._activeModel = params.model;
954-
this.enableAgents = params.enableAgents ?? true;
954+
this.enableAgents = params.enableAgents ?? false;
955955
this.agents = params.agents ?? {};
956956
this.disableLLMCorrection = params.disableLLMCorrection ?? true;
957957
this.planEnabled = params.plan ?? true;

schemas/settings.schema.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1971,8 +1971,8 @@
19711971
"enableAgents": {
19721972
"title": "Enable Agents",
19731973
"description": "Enable local and remote subagents.",
1974-
"markdownDescription": "Enable local and remote subagents.\n\n- Category: `Experimental`\n- Requires restart: `yes`\n- Default: `true`",
1975-
"default": true,
1974+
"markdownDescription": "Enable local and remote subagents.\n\n- Category: `Experimental`\n- Requires restart: `yes`\n- Default: `false`",
1975+
"default": false,
19761976
"type": "boolean"
19771977
},
19781978
"extensionManagement": {

0 commit comments

Comments
 (0)