Skip to content

Commit 83d9596

Browse files
Abhijit-2592galdawave
authored andcommitted
feat(cli): hide workspace policy update dialog and auto-accept by default (#20351)
1 parent 72033d0 commit 83d9596

3 files changed

Lines changed: 151 additions & 54 deletions

File tree

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

Lines changed: 57 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
88
import * as fs from 'node:fs';
99
import * as path from 'node:path';
1010
import * as os from 'node:os';
11-
import { resolveWorkspacePolicyState } from './policy.js';
11+
import {
12+
resolveWorkspacePolicyState,
13+
autoAcceptWorkspacePolicies,
14+
setAutoAcceptWorkspacePolicies,
15+
} from './policy.js';
1216
import { writeToStderr } from '@google/gemini-cli-core';
1317

1418
// Mock debugLogger to avoid noise in test output
@@ -68,24 +72,18 @@ describe('resolveWorkspacePolicyState', () => {
6872
fs.mkdirSync(policiesDir, { recursive: true });
6973
fs.writeFileSync(path.join(policiesDir, 'policy.toml'), 'rules = []');
7074

71-
// First call to establish integrity (interactive accept)
75+
// First call to establish integrity (interactive auto-accept)
7276
const firstResult = await resolveWorkspacePolicyState({
7377
cwd: workspaceDir,
7478
trustedFolder: true,
7579
interactive: true,
7680
});
77-
expect(firstResult.policyUpdateConfirmationRequest).toBeDefined();
78-
79-
// Establish integrity manually as if accepted
80-
const { PolicyIntegrityManager } = await import('@google/gemini-cli-core');
81-
const integrityManager = new PolicyIntegrityManager();
82-
await integrityManager.acceptIntegrity(
83-
'workspace',
84-
workspaceDir,
85-
firstResult.policyUpdateConfirmationRequest!.newHash,
86-
);
81+
expect(firstResult.workspacePoliciesDir).toBe(policiesDir);
82+
expect(firstResult.policyUpdateConfirmationRequest).toBeUndefined();
83+
expect(writeToStderr).not.toHaveBeenCalled();
8784

8885
// Second call should match
86+
8987
const result = await resolveWorkspacePolicyState({
9088
cwd: workspaceDir,
9189
trustedFolder: true,
@@ -107,26 +105,33 @@ describe('resolveWorkspacePolicyState', () => {
107105
expect(result.policyUpdateConfirmationRequest).toBeUndefined();
108106
});
109107

110-
it('should return confirmation request if changed in interactive mode', async () => {
111-
fs.mkdirSync(policiesDir, { recursive: true });
112-
fs.writeFileSync(path.join(policiesDir, 'policy.toml'), 'rules = []');
108+
it('should return confirmation request if changed in interactive mode when AUTO_ACCEPT is false', async () => {
109+
const originalValue = autoAcceptWorkspacePolicies;
110+
setAutoAcceptWorkspacePolicies(false);
113111

114-
const result = await resolveWorkspacePolicyState({
115-
cwd: workspaceDir,
116-
trustedFolder: true,
117-
interactive: true,
118-
});
112+
try {
113+
fs.mkdirSync(policiesDir, { recursive: true });
114+
fs.writeFileSync(path.join(policiesDir, 'policy.toml'), 'rules = []');
119115

120-
expect(result.workspacePoliciesDir).toBeUndefined();
121-
expect(result.policyUpdateConfirmationRequest).toEqual({
122-
scope: 'workspace',
123-
identifier: workspaceDir,
124-
policyDir: policiesDir,
125-
newHash: expect.any(String),
126-
});
116+
const result = await resolveWorkspacePolicyState({
117+
cwd: workspaceDir,
118+
trustedFolder: true,
119+
interactive: true,
120+
});
121+
122+
expect(result.workspacePoliciesDir).toBeUndefined();
123+
expect(result.policyUpdateConfirmationRequest).toEqual({
124+
scope: 'workspace',
125+
identifier: workspaceDir,
126+
policyDir: policiesDir,
127+
newHash: expect.any(String),
128+
});
129+
} finally {
130+
setAutoAcceptWorkspacePolicies(originalValue);
131+
}
127132
});
128133

129-
it('should warn and auto-accept if changed in non-interactive mode', async () => {
134+
it('should warn and auto-accept if changed in non-interactive mode when AUTO_ACCEPT is true', async () => {
130135
fs.mkdirSync(policiesDir, { recursive: true });
131136
fs.writeFileSync(path.join(policiesDir, 'policy.toml'), 'rules = []');
132137

@@ -143,6 +148,30 @@ describe('resolveWorkspacePolicyState', () => {
143148
);
144149
});
145150

151+
it('should warn and auto-accept if changed in non-interactive mode when AUTO_ACCEPT is false', async () => {
152+
const originalValue = autoAcceptWorkspacePolicies;
153+
setAutoAcceptWorkspacePolicies(false);
154+
155+
try {
156+
fs.mkdirSync(policiesDir, { recursive: true });
157+
fs.writeFileSync(path.join(policiesDir, 'policy.toml'), 'rules = []');
158+
159+
const result = await resolveWorkspacePolicyState({
160+
cwd: workspaceDir,
161+
trustedFolder: true,
162+
interactive: false,
163+
});
164+
165+
expect(result.workspacePoliciesDir).toBe(policiesDir);
166+
expect(result.policyUpdateConfirmationRequest).toBeUndefined();
167+
expect(writeToStderr).toHaveBeenCalledWith(
168+
expect.stringContaining('Automatically accepting and loading'),
169+
);
170+
} finally {
171+
setAutoAcceptWorkspacePolicies(originalValue);
172+
}
173+
});
174+
146175
it('should not return workspace policies if cwd is the home directory', async () => {
147176
const policiesDir = path.join(tempDir, '.gemini', 'policies');
148177
fs.mkdirSync(policiesDir, { recursive: true });

packages/cli/src/config/policy.ts

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,24 @@ import {
1717
Storage,
1818
type PolicyUpdateConfirmationRequest,
1919
writeToStderr,
20+
debugLogger,
2021
} from '@google/gemini-cli-core';
2122
import { type Settings } from './settings.js';
2223

24+
/**
25+
* Temporary flag to automatically accept workspace policies to reduce friction.
26+
* Exported as 'let' to allow monkey patching in tests via the setter.
27+
*/
28+
export let autoAcceptWorkspacePolicies = true;
29+
30+
/**
31+
* Sets the autoAcceptWorkspacePolicies flag.
32+
* Used primarily for testing purposes.
33+
*/
34+
export function setAutoAcceptWorkspacePolicies(value: boolean) {
35+
autoAcceptWorkspacePolicies = value;
36+
}
37+
2338
export async function createPolicyEngineConfig(
2439
settings: Settings,
2540
approvalMode: ApprovalMode,
@@ -91,26 +106,32 @@ export async function resolveWorkspacePolicyState(options: {
91106
) {
92107
// No workspace policies found
93108
workspacePoliciesDir = undefined;
94-
} else if (interactive) {
95-
// Policies changed or are new, and we are in interactive mode
109+
} else if (interactive && !autoAcceptWorkspacePolicies) {
110+
// Policies changed or are new, and we are in interactive mode and auto-accept is disabled
96111
policyUpdateConfirmationRequest = {
97112
scope: 'workspace',
98113
identifier: cwd,
99114
policyDir: potentialWorkspacePoliciesDir,
100115
newHash: integrityResult.hash,
101116
};
102117
} else {
103-
// Non-interactive mode: warn and automatically accept/load
118+
// Non-interactive mode or auto-accept is enabled: automatically accept/load
104119
await integrityManager.acceptIntegrity(
105120
'workspace',
106121
cwd,
107122
integrityResult.hash,
108123
);
109124
workspacePoliciesDir = potentialWorkspacePoliciesDir;
110-
// debugLogger.warn here doesn't show up in the terminal. It is showing up only in debug mode on the debug console
111-
writeToStderr(
112-
'WARNING: Workspace policies changed or are new. Automatically accepting and loading them in non-interactive mode.\n',
113-
);
125+
126+
if (!interactive) {
127+
writeToStderr(
128+
'WARNING: Workspace policies changed or are new. Automatically accepting and loading them.\n',
129+
);
130+
} else {
131+
debugLogger.warn(
132+
'Workspace policies changed or are new. Automatically accepting and loading them.',
133+
);
134+
}
114135
}
115136
}
116137

packages/cli/src/config/workspace-policy-cli.test.ts

Lines changed: 66 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { loadCliConfig, type CliArgs } from './config.js';
1010
import { createTestMergedSettings } from './settings.js';
1111
import * as ServerConfig from '@google/gemini-cli-core';
1212
import { isWorkspaceTrusted } from './trustedFolders.js';
13+
import * as Policy from './policy.js';
1314

1415
// Mock dependencies
1516
vi.mock('./trustedFolders.js', () => ({
@@ -164,7 +165,7 @@ describe('Workspace-Level Policy CLI Integration', () => {
164165
);
165166
});
166167

167-
it('should set policyUpdateConfirmationRequest if integrity MISMATCH in interactive mode', async () => {
168+
it('should automatically accept and load workspacePoliciesDir if integrity MISMATCH in interactive mode when AUTO_ACCEPT is true', async () => {
168169
vi.mocked(isWorkspaceTrusted).mockReturnValue({
169170
isTrusted: true,
170171
source: 'file',
@@ -186,24 +187,23 @@ describe('Workspace-Level Policy CLI Integration', () => {
186187
cwd: MOCK_CWD,
187188
});
188189

189-
expect(config.getPolicyUpdateConfirmationRequest()).toEqual({
190-
scope: 'workspace',
191-
identifier: MOCK_CWD,
192-
policyDir: expect.stringContaining(path.join('.gemini', 'policies')),
193-
newHash: 'new-hash',
194-
});
195-
// In interactive mode without accept flag, it waits for user confirmation (handled by UI),
196-
// so it currently DOES NOT pass the directory to createPolicyEngineConfig yet.
197-
// The UI will handle the confirmation and reload/update.
190+
expect(config.getPolicyUpdateConfirmationRequest()).toBeUndefined();
191+
expect(mockAcceptIntegrity).toHaveBeenCalledWith(
192+
'workspace',
193+
MOCK_CWD,
194+
'new-hash',
195+
);
198196
expect(ServerConfig.createPolicyEngineConfig).toHaveBeenCalledWith(
199197
expect.objectContaining({
200-
workspacePoliciesDir: undefined,
198+
workspacePoliciesDir: expect.stringContaining(
199+
path.join('.gemini', 'policies'),
200+
),
201201
}),
202202
expect.anything(),
203203
);
204204
});
205205

206-
it('should set policyUpdateConfirmationRequest if integrity is NEW with files (first time seen) in interactive mode', async () => {
206+
it('should automatically accept and load workspacePoliciesDir if integrity is NEW in interactive mode when AUTO_ACCEPT is true', async () => {
207207
vi.mocked(isWorkspaceTrusted).mockReturnValue({
208208
isTrusted: true,
209209
source: 'file',
@@ -222,18 +222,65 @@ describe('Workspace-Level Policy CLI Integration', () => {
222222
cwd: MOCK_CWD,
223223
});
224224

225-
expect(config.getPolicyUpdateConfirmationRequest()).toEqual({
226-
scope: 'workspace',
227-
identifier: MOCK_CWD,
228-
policyDir: expect.stringContaining(path.join('.gemini', 'policies')),
229-
newHash: 'new-hash',
230-
});
225+
expect(config.getPolicyUpdateConfirmationRequest()).toBeUndefined();
226+
expect(mockAcceptIntegrity).toHaveBeenCalledWith(
227+
'workspace',
228+
MOCK_CWD,
229+
'new-hash',
230+
);
231231

232232
expect(ServerConfig.createPolicyEngineConfig).toHaveBeenCalledWith(
233233
expect.objectContaining({
234-
workspacePoliciesDir: undefined,
234+
workspacePoliciesDir: expect.stringContaining(
235+
path.join('.gemini', 'policies'),
236+
),
235237
}),
236238
expect.anything(),
237239
);
238240
});
241+
242+
it('should set policyUpdateConfirmationRequest if integrity MISMATCH in interactive mode when AUTO_ACCEPT is false', async () => {
243+
// Monkey patch autoAcceptWorkspacePolicies using setter
244+
const originalValue = Policy.autoAcceptWorkspacePolicies;
245+
Policy.setAutoAcceptWorkspacePolicies(false);
246+
247+
try {
248+
vi.mocked(isWorkspaceTrusted).mockReturnValue({
249+
isTrusted: true,
250+
source: 'file',
251+
});
252+
mockCheckIntegrity.mockResolvedValue({
253+
status: 'mismatch',
254+
hash: 'new-hash',
255+
fileCount: 1,
256+
});
257+
vi.mocked(ServerConfig.isHeadlessMode).mockReturnValue(false); // Interactive
258+
259+
const settings = createTestMergedSettings();
260+
const argv = {
261+
query: 'test',
262+
promptInteractive: 'test',
263+
} as unknown as CliArgs;
264+
265+
const config = await loadCliConfig(settings, 'test-session', argv, {
266+
cwd: MOCK_CWD,
267+
});
268+
269+
expect(config.getPolicyUpdateConfirmationRequest()).toEqual({
270+
scope: 'workspace',
271+
identifier: MOCK_CWD,
272+
policyDir: expect.stringContaining(path.join('.gemini', 'policies')),
273+
newHash: 'new-hash',
274+
});
275+
expect(ServerConfig.createPolicyEngineConfig).toHaveBeenCalledWith(
276+
expect.objectContaining({
277+
workspacePoliciesDir: undefined,
278+
}),
279+
expect.anything(),
280+
);
281+
} finally {
282+
// Restore for other tests
283+
Policy.setAutoAcceptWorkspacePolicies(originalValue);
284+
}
285+
});
239286
});

0 commit comments

Comments
 (0)