mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 05:12:55 -07:00
feat(admin): provide actionable error messages for disabled features (#17815)
This commit is contained in:
@@ -1255,7 +1255,7 @@ describe('Approval mode tool exclusion logic', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await expect(loadCliConfig(settings, 'test-session', argv)).rejects.toThrow(
|
await expect(loadCliConfig(settings, 'test-session', argv)).rejects.toThrow(
|
||||||
'Cannot start in YOLO mode since it is disabled by your admin',
|
'YOLO mode is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2928,7 +2928,7 @@ describe('loadCliConfig disableYoloMode', () => {
|
|||||||
security: { disableYoloMode: true },
|
security: { disableYoloMode: true },
|
||||||
});
|
});
|
||||||
await expect(loadCliConfig(settings, 'test-session', argv)).rejects.toThrow(
|
await expect(loadCliConfig(settings, 'test-session', argv)).rejects.toThrow(
|
||||||
'Cannot start in YOLO mode since it is disabled by your admin',
|
'YOLO mode is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -2960,7 +2960,7 @@ describe('loadCliConfig secureModeEnabled', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await expect(loadCliConfig(settings, 'test-session', argv)).rejects.toThrow(
|
await expect(loadCliConfig(settings, 'test-session', argv)).rejects.toThrow(
|
||||||
'Cannot start in YOLO mode since it is disabled by your admin',
|
'YOLO mode is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2974,7 +2974,7 @@ describe('loadCliConfig secureModeEnabled', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await expect(loadCliConfig(settings, 'test-session', argv)).rejects.toThrow(
|
await expect(loadCliConfig(settings, 'test-session', argv)).rejects.toThrow(
|
||||||
'Cannot start in YOLO mode since it is disabled by your admin',
|
'YOLO mode is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import {
|
|||||||
type OutputFormat,
|
type OutputFormat,
|
||||||
coreEvents,
|
coreEvents,
|
||||||
GEMINI_MODEL_ALIAS_AUTO,
|
GEMINI_MODEL_ALIAS_AUTO,
|
||||||
|
getAdminErrorMessage,
|
||||||
} from '@google/gemini-cli-core';
|
} from '@google/gemini-cli-core';
|
||||||
import {
|
import {
|
||||||
type Settings,
|
type Settings,
|
||||||
@@ -550,7 +551,7 @@ export async function loadCliConfig(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
throw new FatalConfigError(
|
throw new FatalConfigError(
|
||||||
'Cannot start in YOLO mode since it is disabled by your admin',
|
getAdminErrorMessage('YOLO mode', undefined /* config */),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (approvalMode === ApprovalMode.YOLO) {
|
} else if (approvalMode === ApprovalMode.YOLO) {
|
||||||
|
|||||||
@@ -16,11 +16,10 @@ import type { ArgumentsCamelCase, CommandModule } from 'yargs';
|
|||||||
import type { MergedSettings } from './config/settings.js';
|
import type { MergedSettings } from './config/settings.js';
|
||||||
import type { MockInstance } from 'vitest';
|
import type { MockInstance } from 'vitest';
|
||||||
|
|
||||||
const { mockRunExitCleanup, mockDebugLogger } = vi.hoisted(() => ({
|
const { mockRunExitCleanup, mockCoreEvents } = vi.hoisted(() => ({
|
||||||
mockRunExitCleanup: vi.fn(),
|
mockRunExitCleanup: vi.fn(),
|
||||||
mockDebugLogger: {
|
mockCoreEvents: {
|
||||||
log: vi.fn(),
|
emitFeedback: vi.fn(),
|
||||||
error: vi.fn(),
|
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -28,7 +27,7 @@ vi.mock('@google/gemini-cli-core', async () => {
|
|||||||
const actual = await vi.importActual('@google/gemini-cli-core');
|
const actual = await vi.importActual('@google/gemini-cli-core');
|
||||||
return {
|
return {
|
||||||
...actual,
|
...actual,
|
||||||
debugLogger: mockDebugLogger,
|
coreEvents: mockCoreEvents,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -55,8 +54,7 @@ describe('deferred', () => {
|
|||||||
describe('runDeferredCommand', () => {
|
describe('runDeferredCommand', () => {
|
||||||
it('should do nothing if no deferred command is set', async () => {
|
it('should do nothing if no deferred command is set', async () => {
|
||||||
await runDeferredCommand(createMockSettings());
|
await runDeferredCommand(createMockSettings());
|
||||||
expect(mockDebugLogger.log).not.toHaveBeenCalled();
|
expect(mockCoreEvents.emitFeedback).not.toHaveBeenCalled();
|
||||||
expect(mockDebugLogger.error).not.toHaveBeenCalled();
|
|
||||||
expect(mockExit).not.toHaveBeenCalled();
|
expect(mockExit).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -85,8 +83,9 @@ describe('deferred', () => {
|
|||||||
const settings = createMockSettings({ mcp: { enabled: false } });
|
const settings = createMockSettings({ mcp: { enabled: false } });
|
||||||
await runDeferredCommand(settings);
|
await runDeferredCommand(settings);
|
||||||
|
|
||||||
expect(mockDebugLogger.error).toHaveBeenCalledWith(
|
expect(mockCoreEvents.emitFeedback).toHaveBeenCalledWith(
|
||||||
'Error: MCP is disabled by your admin.',
|
'error',
|
||||||
|
'MCP is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli',
|
||||||
);
|
);
|
||||||
expect(mockRunExitCleanup).toHaveBeenCalled();
|
expect(mockRunExitCleanup).toHaveBeenCalled();
|
||||||
expect(mockExit).toHaveBeenCalledWith(ExitCodes.FATAL_CONFIG_ERROR);
|
expect(mockExit).toHaveBeenCalledWith(ExitCodes.FATAL_CONFIG_ERROR);
|
||||||
@@ -102,8 +101,9 @@ describe('deferred', () => {
|
|||||||
const settings = createMockSettings({ extensions: { enabled: false } });
|
const settings = createMockSettings({ extensions: { enabled: false } });
|
||||||
await runDeferredCommand(settings);
|
await runDeferredCommand(settings);
|
||||||
|
|
||||||
expect(mockDebugLogger.error).toHaveBeenCalledWith(
|
expect(mockCoreEvents.emitFeedback).toHaveBeenCalledWith(
|
||||||
'Error: Extensions are disabled by your admin.',
|
'error',
|
||||||
|
'Extensions is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli',
|
||||||
);
|
);
|
||||||
expect(mockRunExitCleanup).toHaveBeenCalled();
|
expect(mockRunExitCleanup).toHaveBeenCalled();
|
||||||
expect(mockExit).toHaveBeenCalledWith(ExitCodes.FATAL_CONFIG_ERROR);
|
expect(mockExit).toHaveBeenCalledWith(ExitCodes.FATAL_CONFIG_ERROR);
|
||||||
@@ -119,8 +119,9 @@ describe('deferred', () => {
|
|||||||
const settings = createMockSettings({ skills: { enabled: false } });
|
const settings = createMockSettings({ skills: { enabled: false } });
|
||||||
await runDeferredCommand(settings);
|
await runDeferredCommand(settings);
|
||||||
|
|
||||||
expect(mockDebugLogger.error).toHaveBeenCalledWith(
|
expect(mockCoreEvents.emitFeedback).toHaveBeenCalledWith(
|
||||||
'Error: Agent skills are disabled by your admin.',
|
'error',
|
||||||
|
'Agent skills is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli',
|
||||||
);
|
);
|
||||||
expect(mockRunExitCleanup).toHaveBeenCalled();
|
expect(mockRunExitCleanup).toHaveBeenCalled();
|
||||||
expect(mockExit).toHaveBeenCalledWith(ExitCodes.FATAL_CONFIG_ERROR);
|
expect(mockExit).toHaveBeenCalledWith(ExitCodes.FATAL_CONFIG_ERROR);
|
||||||
@@ -183,8 +184,9 @@ describe('deferred', () => {
|
|||||||
const mcpSettings = createMockSettings({ mcp: { enabled: false } });
|
const mcpSettings = createMockSettings({ mcp: { enabled: false } });
|
||||||
await runDeferredCommand(mcpSettings);
|
await runDeferredCommand(mcpSettings);
|
||||||
|
|
||||||
expect(mockDebugLogger.error).toHaveBeenCalledWith(
|
expect(mockCoreEvents.emitFeedback).toHaveBeenCalledWith(
|
||||||
'Error: MCP is disabled by your admin.',
|
'error',
|
||||||
|
'MCP is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,11 @@
|
|||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
import type { ArgumentsCamelCase, CommandModule } from 'yargs';
|
import type { ArgumentsCamelCase, CommandModule } from 'yargs';
|
||||||
import { debugLogger, ExitCodes } from '@google/gemini-cli-core';
|
import {
|
||||||
|
coreEvents,
|
||||||
|
ExitCodes,
|
||||||
|
getAdminErrorMessage,
|
||||||
|
} from '@google/gemini-cli-core';
|
||||||
import { runExitCleanup } from './utils/cleanup.js';
|
import { runExitCleanup } from './utils/cleanup.js';
|
||||||
import type { MergedSettings } from './config/settings.js';
|
import type { MergedSettings } from './config/settings.js';
|
||||||
import process from 'node:process';
|
import process from 'node:process';
|
||||||
@@ -30,7 +34,10 @@ export async function runDeferredCommand(settings: MergedSettings) {
|
|||||||
const commandName = deferredCommand.commandName;
|
const commandName = deferredCommand.commandName;
|
||||||
|
|
||||||
if (commandName === 'mcp' && adminSettings?.mcp?.enabled === false) {
|
if (commandName === 'mcp' && adminSettings?.mcp?.enabled === false) {
|
||||||
debugLogger.error('Error: MCP is disabled by your admin.');
|
coreEvents.emitFeedback(
|
||||||
|
'error',
|
||||||
|
getAdminErrorMessage('MCP', undefined /* config */),
|
||||||
|
);
|
||||||
await runExitCleanup();
|
await runExitCleanup();
|
||||||
process.exit(ExitCodes.FATAL_CONFIG_ERROR);
|
process.exit(ExitCodes.FATAL_CONFIG_ERROR);
|
||||||
}
|
}
|
||||||
@@ -39,13 +46,19 @@ export async function runDeferredCommand(settings: MergedSettings) {
|
|||||||
commandName === 'extensions' &&
|
commandName === 'extensions' &&
|
||||||
adminSettings?.extensions?.enabled === false
|
adminSettings?.extensions?.enabled === false
|
||||||
) {
|
) {
|
||||||
debugLogger.error('Error: Extensions are disabled by your admin.');
|
coreEvents.emitFeedback(
|
||||||
|
'error',
|
||||||
|
getAdminErrorMessage('Extensions', undefined /* config */),
|
||||||
|
);
|
||||||
await runExitCleanup();
|
await runExitCleanup();
|
||||||
process.exit(ExitCodes.FATAL_CONFIG_ERROR);
|
process.exit(ExitCodes.FATAL_CONFIG_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (commandName === 'skills' && adminSettings?.skills?.enabled === false) {
|
if (commandName === 'skills' && adminSettings?.skills?.enabled === false) {
|
||||||
debugLogger.error('Error: Agent skills are disabled by your admin.');
|
coreEvents.emitFeedback(
|
||||||
|
'error',
|
||||||
|
getAdminErrorMessage('Agent skills', undefined /* config */),
|
||||||
|
);
|
||||||
await runExitCleanup();
|
await runExitCleanup();
|
||||||
process.exit(ExitCodes.FATAL_CONFIG_ERROR);
|
process.exit(ExitCodes.FATAL_CONFIG_ERROR);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,11 @@ import {
|
|||||||
type CommandContext,
|
type CommandContext,
|
||||||
} from '../ui/commands/types.js';
|
} from '../ui/commands/types.js';
|
||||||
import type { MessageActionReturn, Config } from '@google/gemini-cli-core';
|
import type { MessageActionReturn, Config } from '@google/gemini-cli-core';
|
||||||
import { isNightly, startupProfiler } from '@google/gemini-cli-core';
|
import {
|
||||||
|
isNightly,
|
||||||
|
startupProfiler,
|
||||||
|
getAdminErrorMessage,
|
||||||
|
} from '@google/gemini-cli-core';
|
||||||
import { aboutCommand } from '../ui/commands/aboutCommand.js';
|
import { aboutCommand } from '../ui/commands/aboutCommand.js';
|
||||||
import { agentsCommand } from '../ui/commands/agentsCommand.js';
|
import { agentsCommand } from '../ui/commands/agentsCommand.js';
|
||||||
import { authCommand } from '../ui/commands/authCommand.js';
|
import { authCommand } from '../ui/commands/authCommand.js';
|
||||||
@@ -102,7 +106,10 @@ export class BuiltinCommandLoader implements ICommandLoader {
|
|||||||
): Promise<MessageActionReturn> => ({
|
): Promise<MessageActionReturn> => ({
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
messageType: 'error',
|
||||||
content: 'Extensions are disabled by your admin.',
|
content: getAdminErrorMessage(
|
||||||
|
'Extensions',
|
||||||
|
this.config ?? undefined,
|
||||||
|
),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -127,7 +134,7 @@ export class BuiltinCommandLoader implements ICommandLoader {
|
|||||||
): Promise<MessageActionReturn> => ({
|
): Promise<MessageActionReturn> => ({
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
messageType: 'error',
|
||||||
content: 'MCP is disabled by your admin.',
|
content: getAdminErrorMessage('MCP', this.config ?? undefined),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -158,7 +165,10 @@ export class BuiltinCommandLoader implements ICommandLoader {
|
|||||||
): Promise<MessageActionReturn> => ({
|
): Promise<MessageActionReturn> => ({
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
messageType: 'error',
|
||||||
content: 'Agent skills are disabled by your admin.',
|
content: getAdminErrorMessage(
|
||||||
|
'Agent skills',
|
||||||
|
this.config ?? undefined,
|
||||||
|
),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ describe('skillsCommand', () => {
|
|||||||
(name: string) => skills.find((s) => s.name === name) ?? null,
|
(name: string) => skills.find((s) => s.name === name) ?? null,
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
|
getContentGenerator: vi.fn(),
|
||||||
} as unknown as Config,
|
} as unknown as Config,
|
||||||
settings: {
|
settings: {
|
||||||
merged: createTestMergedSettings({ skills: { disabled: [] } }),
|
merged: createTestMergedSettings({ skills: { disabled: [] } }),
|
||||||
@@ -367,7 +368,7 @@ describe('skillsCommand', () => {
|
|||||||
expect(context.ui.addItem).toHaveBeenCalledWith(
|
expect(context.ui.addItem).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
type: MessageType.ERROR,
|
type: MessageType.ERROR,
|
||||||
text: 'Agent skills are disabled by your admin.',
|
text: 'Agent skills is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli',
|
||||||
}),
|
}),
|
||||||
expect.any(Number),
|
expect.any(Number),
|
||||||
);
|
);
|
||||||
@@ -385,7 +386,7 @@ describe('skillsCommand', () => {
|
|||||||
expect(context.ui.addItem).toHaveBeenCalledWith(
|
expect(context.ui.addItem).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
type: MessageType.ERROR,
|
type: MessageType.ERROR,
|
||||||
text: 'Agent skills are disabled by your admin.',
|
text: 'Agent skills is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli',
|
||||||
}),
|
}),
|
||||||
expect.any(Number),
|
expect.any(Number),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,13 +11,15 @@ import {
|
|||||||
CommandKind,
|
CommandKind,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
import {
|
import {
|
||||||
MessageType,
|
|
||||||
type HistoryItemSkillsList,
|
|
||||||
type HistoryItemInfo,
|
type HistoryItemInfo,
|
||||||
|
type HistoryItemSkillsList,
|
||||||
|
MessageType,
|
||||||
} from '../types.js';
|
} from '../types.js';
|
||||||
import { SettingScope } from '../../config/settings.js';
|
import { disableSkill, enableSkill } from '../../utils/skillSettings.js';
|
||||||
import { enableSkill, disableSkill } from '../../utils/skillSettings.js';
|
|
||||||
|
import { getAdminErrorMessage } from '@google/gemini-cli-core';
|
||||||
import { renderSkillActionFeedback } from '../../utils/skillUtils.js';
|
import { renderSkillActionFeedback } from '../../utils/skillUtils.js';
|
||||||
|
import { SettingScope } from '../../config/settings.js';
|
||||||
|
|
||||||
async function listAction(
|
async function listAction(
|
||||||
context: CommandContext,
|
context: CommandContext,
|
||||||
@@ -83,7 +85,10 @@ async function disableAction(
|
|||||||
context.ui.addItem(
|
context.ui.addItem(
|
||||||
{
|
{
|
||||||
type: MessageType.ERROR,
|
type: MessageType.ERROR,
|
||||||
text: 'Agent skills are disabled by your admin.',
|
text: getAdminErrorMessage(
|
||||||
|
'Agent skills',
|
||||||
|
context.services.config ?? undefined,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
Date.now(),
|
Date.now(),
|
||||||
);
|
);
|
||||||
@@ -141,7 +146,10 @@ async function enableAction(
|
|||||||
context.ui.addItem(
|
context.ui.addItem(
|
||||||
{
|
{
|
||||||
type: MessageType.ERROR,
|
type: MessageType.ERROR,
|
||||||
text: 'Agent skills are disabled by your admin.',
|
text: getAdminErrorMessage(
|
||||||
|
'Agent skills',
|
||||||
|
context.services.config ?? undefined,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
Date.now(),
|
Date.now(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ vi.mock('@google/gemini-cli-core', async () => {
|
|||||||
return {
|
return {
|
||||||
...actualServerModule,
|
...actualServerModule,
|
||||||
Config: vi.fn(),
|
Config: vi.fn(),
|
||||||
|
getAdminErrorMessage: vi.fn(
|
||||||
|
(featureName: string) => `[Mock] ${featureName} is disabled`,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -52,6 +55,9 @@ interface MockConfigInstanceShape {
|
|||||||
getUserMemory: Mock<() => string>;
|
getUserMemory: Mock<() => string>;
|
||||||
getGeminiMdFileCount: Mock<() => number>;
|
getGeminiMdFileCount: Mock<() => number>;
|
||||||
getToolRegistry: Mock<() => { discoverTools: Mock<() => void> }>;
|
getToolRegistry: Mock<() => { discoverTools: Mock<() => void> }>;
|
||||||
|
getRemoteAdminSettings: Mock<
|
||||||
|
() => { strictModeDisabled?: boolean; mcpEnabled?: boolean } | undefined
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type UseKeypressHandler = (key: Key) => void;
|
type UseKeypressHandler = (key: Key) => void;
|
||||||
@@ -109,6 +115,9 @@ describe('useApprovalModeIndicator', () => {
|
|||||||
.mockReturnValue({ discoverTools: vi.fn() }) as Mock<
|
.mockReturnValue({ discoverTools: vi.fn() }) as Mock<
|
||||||
() => { discoverTools: Mock<() => void> }
|
() => { discoverTools: Mock<() => void> }
|
||||||
>,
|
>,
|
||||||
|
getRemoteAdminSettings: vi.fn().mockReturnValue(undefined) as Mock<
|
||||||
|
() => { strictModeDisabled?: boolean } | undefined
|
||||||
|
>,
|
||||||
};
|
};
|
||||||
instanceSetApprovalModeMock.mockImplementation((value: ApprovalMode) => {
|
instanceSetApprovalModeMock.mockImplementation((value: ApprovalMode) => {
|
||||||
instanceGetApprovalModeMock.mockReturnValue(value);
|
instanceGetApprovalModeMock.mockReturnValue(value);
|
||||||
@@ -517,6 +526,9 @@ describe('useApprovalModeIndicator', () => {
|
|||||||
|
|
||||||
it('should not enable YOLO mode when Ctrl+Y is pressed and add an info message', () => {
|
it('should not enable YOLO mode when Ctrl+Y is pressed and add an info message', () => {
|
||||||
mockConfigInstance.getApprovalMode.mockReturnValue(ApprovalMode.DEFAULT);
|
mockConfigInstance.getApprovalMode.mockReturnValue(ApprovalMode.DEFAULT);
|
||||||
|
mockConfigInstance.getRemoteAdminSettings.mockReturnValue({
|
||||||
|
strictModeDisabled: true,
|
||||||
|
});
|
||||||
const mockAddItem = vi.fn();
|
const mockAddItem = vi.fn();
|
||||||
const { result } = renderHook(() =>
|
const { result } = renderHook(() =>
|
||||||
useApprovalModeIndicator({
|
useApprovalModeIndicator({
|
||||||
@@ -544,6 +556,58 @@ describe('useApprovalModeIndicator', () => {
|
|||||||
// The mode should not change
|
// The mode should not change
|
||||||
expect(result.current).toBe(ApprovalMode.DEFAULT);
|
expect(result.current).toBe(ApprovalMode.DEFAULT);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should show admin error message when YOLO mode is disabled by admin', () => {
|
||||||
|
mockConfigInstance.getApprovalMode.mockReturnValue(ApprovalMode.DEFAULT);
|
||||||
|
mockConfigInstance.getRemoteAdminSettings.mockReturnValue({
|
||||||
|
mcpEnabled: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockAddItem = vi.fn();
|
||||||
|
renderHook(() =>
|
||||||
|
useApprovalModeIndicator({
|
||||||
|
config: mockConfigInstance as unknown as ActualConfigType,
|
||||||
|
addItem: mockAddItem,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
capturedUseKeypressHandler({ name: 'y', ctrl: true } as Key);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockAddItem).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
type: MessageType.WARNING,
|
||||||
|
text: '[Mock] YOLO mode is disabled',
|
||||||
|
},
|
||||||
|
expect.any(Number),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show default error message when admin settings are empty', () => {
|
||||||
|
mockConfigInstance.getApprovalMode.mockReturnValue(ApprovalMode.DEFAULT);
|
||||||
|
mockConfigInstance.getRemoteAdminSettings.mockReturnValue({});
|
||||||
|
|
||||||
|
const mockAddItem = vi.fn();
|
||||||
|
renderHook(() =>
|
||||||
|
useApprovalModeIndicator({
|
||||||
|
config: mockConfigInstance as unknown as ActualConfigType,
|
||||||
|
addItem: mockAddItem,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
capturedUseKeypressHandler({ name: 'y', ctrl: true } as Key);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockAddItem).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
type: MessageType.WARNING,
|
||||||
|
text: 'You cannot enter YOLO mode since it is disabled in your settings.',
|
||||||
|
},
|
||||||
|
expect.any(Number),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call onApprovalModeChange when switching to YOLO mode', () => {
|
it('should call onApprovalModeChange when switching to YOLO mode', () => {
|
||||||
|
|||||||
@@ -5,7 +5,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { ApprovalMode, type Config } from '@google/gemini-cli-core';
|
import {
|
||||||
|
ApprovalMode,
|
||||||
|
type Config,
|
||||||
|
getAdminErrorMessage,
|
||||||
|
} from '@google/gemini-cli-core';
|
||||||
import { useKeypress } from './useKeypress.js';
|
import { useKeypress } from './useKeypress.js';
|
||||||
import { keyMatchers, Command } from '../keyMatchers.js';
|
import { keyMatchers, Command } from '../keyMatchers.js';
|
||||||
import type { HistoryItemWithoutId } from '../types.js';
|
import type { HistoryItemWithoutId } from '../types.js';
|
||||||
@@ -41,10 +45,19 @@ export function useApprovalModeIndicator({
|
|||||||
config.getApprovalMode() !== ApprovalMode.YOLO
|
config.getApprovalMode() !== ApprovalMode.YOLO
|
||||||
) {
|
) {
|
||||||
if (addItem) {
|
if (addItem) {
|
||||||
|
let text =
|
||||||
|
'You cannot enter YOLO mode since it is disabled in your settings.';
|
||||||
|
const adminSettings = config.getRemoteAdminSettings();
|
||||||
|
const hasSettings =
|
||||||
|
adminSettings && Object.keys(adminSettings).length > 0;
|
||||||
|
if (hasSettings && !adminSettings.strictModeDisabled) {
|
||||||
|
text = getAdminErrorMessage('YOLO mode', config);
|
||||||
|
}
|
||||||
|
|
||||||
addItem(
|
addItem(
|
||||||
{
|
{
|
||||||
type: MessageType.WARNING,
|
type: MessageType.WARNING,
|
||||||
text: 'You cannot enter YOLO mode since it is disabled in your settings.',
|
text,
|
||||||
},
|
},
|
||||||
Date.now(),
|
Date.now(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -17,8 +17,15 @@ import {
|
|||||||
fetchAdminControls,
|
fetchAdminControls,
|
||||||
sanitizeAdminSettings,
|
sanitizeAdminSettings,
|
||||||
stopAdminControlsPolling,
|
stopAdminControlsPolling,
|
||||||
|
getAdminErrorMessage,
|
||||||
} from './admin_controls.js';
|
} from './admin_controls.js';
|
||||||
import type { CodeAssistServer } from '../server.js';
|
import type { CodeAssistServer } from '../server.js';
|
||||||
|
import type { Config } from '../../config/config.js';
|
||||||
|
import { getCodeAssistServer } from '../codeAssist.js';
|
||||||
|
|
||||||
|
vi.mock('../codeAssist.js', () => ({
|
||||||
|
getCodeAssistServer: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
describe('Admin Controls', () => {
|
describe('Admin Controls', () => {
|
||||||
let mockServer: CodeAssistServer;
|
let mockServer: CodeAssistServer;
|
||||||
@@ -370,6 +377,57 @@ describe('Admin Controls', () => {
|
|||||||
|
|
||||||
// The poll should not have fired again
|
// The poll should not have fired again
|
||||||
expect(mockServer.fetchAdminControls).toHaveBeenCalledTimes(1);
|
expect(mockServer.fetchAdminControls).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockServer.fetchAdminControls).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getAdminErrorMessage', () => {
|
||||||
|
let mockConfig: Config;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockConfig = {} as Config;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include feature name and project ID when present', () => {
|
||||||
|
vi.mocked(getCodeAssistServer).mockReturnValue({
|
||||||
|
projectId: 'test-project-123',
|
||||||
|
} as CodeAssistServer);
|
||||||
|
|
||||||
|
const message = getAdminErrorMessage('Code Completion', mockConfig);
|
||||||
|
|
||||||
|
expect(message).toBe(
|
||||||
|
'Code Completion is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli?project=test-project-123',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include feature name but OMIT project ID when missing', () => {
|
||||||
|
vi.mocked(getCodeAssistServer).mockReturnValue({
|
||||||
|
projectId: undefined,
|
||||||
|
} as CodeAssistServer);
|
||||||
|
|
||||||
|
const message = getAdminErrorMessage('Chat', mockConfig);
|
||||||
|
|
||||||
|
expect(message).toBe(
|
||||||
|
'Chat is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include feature name but OMIT project ID when server is undefined', () => {
|
||||||
|
vi.mocked(getCodeAssistServer).mockReturnValue(undefined);
|
||||||
|
|
||||||
|
const message = getAdminErrorMessage('Chat', mockConfig);
|
||||||
|
|
||||||
|
expect(message).toBe(
|
||||||
|
'Chat is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include feature name but OMIT project ID when config is undefined', () => {
|
||||||
|
const message = getAdminErrorMessage('Chat', undefined);
|
||||||
|
|
||||||
|
expect(message).toBe(
|
||||||
|
'Chat is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli',
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import {
|
|||||||
type FetchAdminControlsResponse,
|
type FetchAdminControlsResponse,
|
||||||
FetchAdminControlsResponseSchema,
|
FetchAdminControlsResponseSchema,
|
||||||
} from '../types.js';
|
} from '../types.js';
|
||||||
|
import { getCodeAssistServer } from '../codeAssist.js';
|
||||||
|
import type { Config } from '../../config/config.js';
|
||||||
|
|
||||||
let pollingInterval: NodeJS.Timeout | undefined;
|
let pollingInterval: NodeJS.Timeout | undefined;
|
||||||
let currentSettings: FetchAdminControlsResponse | undefined;
|
let currentSettings: FetchAdminControlsResponse | undefined;
|
||||||
@@ -132,3 +134,20 @@ export function stopAdminControlsPolling() {
|
|||||||
pollingInterval = undefined;
|
pollingInterval = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a standardized error message for features disabled by admin settings.
|
||||||
|
*
|
||||||
|
* @param featureName The name of the disabled feature
|
||||||
|
* @param config The application config
|
||||||
|
* @returns The formatted error message
|
||||||
|
*/
|
||||||
|
export function getAdminErrorMessage(
|
||||||
|
featureName: string,
|
||||||
|
config: Config | undefined,
|
||||||
|
): string {
|
||||||
|
const server = config ? getCodeAssistServer(config) : undefined;
|
||||||
|
const projectId = server?.projectId;
|
||||||
|
const projectParam = projectId ? `?project=${projectId}` : '';
|
||||||
|
return `${featureName} is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli${projectParam}`;
|
||||||
|
}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ export * from './code_assist/server.js';
|
|||||||
export * from './code_assist/setup.js';
|
export * from './code_assist/setup.js';
|
||||||
export * from './code_assist/types.js';
|
export * from './code_assist/types.js';
|
||||||
export * from './code_assist/telemetry.js';
|
export * from './code_assist/telemetry.js';
|
||||||
|
export * from './code_assist/admin/admin_controls.js';
|
||||||
export * from './core/apiKeyCredentialStorage.js';
|
export * from './core/apiKeyCredentialStorage.js';
|
||||||
|
|
||||||
// Export utilities
|
// Export utilities
|
||||||
|
|||||||
Reference in New Issue
Block a user