feat(admin): support admin-enforced settings for Agent Skills (#16406)

This commit is contained in:
N. Taylor Mullen
2026-01-13 23:40:23 -08:00
committed by GitHub
parent 66e7b479ae
commit bb6c574144
20 changed files with 350 additions and 52 deletions

View File

@@ -637,6 +637,7 @@ export async function loadCliConfig(
const mcpEnabled = settings.admin?.mcp?.enabled ?? true;
const extensionsEnabled = settings.admin?.extensions?.enabled ?? true;
const adminSkillsEnabled = settings.admin?.skills?.enabled ?? true;
return new Config({
sessionId,
@@ -661,6 +662,7 @@ export async function loadCliConfig(
mcpEnabled,
extensionsEnabled,
agents: settings.agents,
adminSkillsEnabled,
allowedMcpServers: mcpEnabled
? (argv.allowedMcpServerNames ?? settings.mcp?.allowed)
: undefined,
@@ -708,6 +710,7 @@ export async function loadCliConfig(
enableAgents: settings.experimental?.enableAgents,
skillsSupport: settings.experimental?.skills,
disabledSkills: settings.skills?.disabled,
experimentalJitContext: settings.experimental?.jitContext,
noBrowser: !!process.env['NO_BROWSER'],
summarizeToolOutput: settings.model?.summarizeToolOutput,
@@ -750,6 +753,8 @@ export async function loadCliConfig(
const refreshedSettings = loadSettings(cwd);
return {
disabledSkills: refreshedSettings.merged.skills?.disabled,
adminSkillsEnabled:
refreshedSettings.merged.admin?.skills?.enabled ?? adminSkillsEnabled,
};
},
});

View File

@@ -26,11 +26,12 @@ vi.mock('node:os', async (importOriginal) => {
// Mock @google/gemini-cli-core
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const actual =
await importOriginal<typeof import('@google/gemini-cli-core')>();
const core = await importOriginal<typeof import('@google/gemini-cli-core')>();
return {
...actual,
...core,
homedir: mockHomedir,
loadAgentsFromDirectory: core.loadAgentsFromDirectory,
loadSkillsFromDir: core.loadSkillsFromDir,
};
});

View File

@@ -10,6 +10,10 @@ import * as path from 'node:path';
import * as os from 'node:os';
import { ExtensionManager } from './extension-manager.js';
import type { Settings } from './settings.js';
import {
loadAgentsFromDirectory,
loadSkillsFromDir,
} from '@google/gemini-cli-core';
let currentTempHome = '';
@@ -24,6 +28,11 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
error: vi.fn(),
warn: vi.fn(),
},
loadAgentsFromDirectory: vi.fn().mockImplementation(async () => ({
agents: [],
errors: [],
})),
loadSkillsFromDir: vi.fn().mockImplementation(async () => []),
};
});
@@ -34,6 +43,11 @@ describe('ExtensionManager Settings Scope', () => {
let extensionDir: string;
beforeEach(async () => {
vi.mocked(loadAgentsFromDirectory).mockResolvedValue({
agents: [],
errors: [],
});
vi.mocked(loadSkillsFromDir).mockResolvedValue([]);
currentTempHome = fs.mkdtempSync(
path.join(os.tmpdir(), 'gemini-cli-test-home-'),
);

View File

@@ -31,6 +31,12 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
return {
...actual,
homedir: mockHomedir,
loadAgentsFromDirectory: vi
.fn()
.mockImplementation(async () => ({ agents: [], errors: [] })),
loadSkillsFromDir: (
await importOriginal<typeof import('@google/gemini-cli-core')>()
).loadSkillsFromDir,
};
});

View File

@@ -23,6 +23,8 @@ import {
ExtensionDisableEvent,
ExtensionEnableEvent,
KeychainTokenStorage,
loadAgentsFromDirectory,
loadSkillsFromDir,
} from '@google/gemini-cli-core';
import { loadSettings, SettingScope } from './settings.js';
import {
@@ -117,6 +119,10 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
listSecrets: vi.fn(),
isAvailable: vi.fn().mockResolvedValue(true),
})),
loadAgentsFromDirectory: vi
.fn()
.mockImplementation(async () => ({ agents: [], errors: [] })),
loadSkillsFromDir: vi.fn().mockImplementation(async () => []),
};
});
@@ -171,6 +177,11 @@ describe('extension tests', () => {
(
KeychainTokenStorage as unknown as ReturnType<typeof vi.fn>
).mockImplementation(() => mockKeychainStorage);
vi.mocked(loadAgentsFromDirectory).mockResolvedValue({
agents: [],
errors: [],
});
vi.mocked(loadSkillsFromDir).mockResolvedValue([]);
tempHomeDir = fs.mkdtempSync(
path.join(os.tmpdir(), 'gemini-cli-test-home-'),
);

View File

@@ -324,23 +324,19 @@ export class LoadedSettings {
setRemoteAdminSettings(remoteSettings: GeminiCodeAssistSetting): void {
const admin: Settings['admin'] = {};
const { secureModeEnabled, mcpSetting, cliFeatureSetting } = remoteSettings;
if (remoteSettings.secureModeEnabled !== undefined) {
admin.secureModeEnabled = remoteSettings.secureModeEnabled;
if (secureModeEnabled !== undefined) {
admin.secureModeEnabled = secureModeEnabled;
}
if (remoteSettings.mcpSetting?.mcpEnabled !== undefined) {
admin.mcp = { enabled: remoteSettings.mcpSetting.mcpEnabled };
if (mcpSetting?.mcpEnabled !== undefined) {
admin.mcp = { enabled: mcpSetting.mcpEnabled };
}
if (
remoteSettings.cliFeatureSetting?.extensionsSetting?.extensionsEnabled !==
undefined
) {
admin.extensions = {
enabled:
remoteSettings.cliFeatureSetting.extensionsSetting.extensionsEnabled,
};
const extensionsSetting = cliFeatureSetting?.extensionsSetting;
if (extensionsSetting?.extensionsEnabled !== undefined) {
admin.extensions = { enabled: extensionsSetting.extensionsEnabled };
}
this._remoteAdminSettings = { admin };

View File

@@ -1842,6 +1842,28 @@ const SETTINGS_SCHEMA = {
},
},
},
skills: {
type: 'object',
label: 'Skills Settings',
category: 'Admin',
requiresRestart: false,
default: {},
description: 'Agent Skills-specific admin settings.',
showInDialog: false,
mergeStrategy: MergeStrategy.REPLACE,
properties: {
enabled: {
type: 'boolean',
label: 'Skills Enabled',
category: 'Admin',
requiresRestart: false,
default: true,
description: 'If false, disallows agent skills from being used.',
showInDialog: false,
mergeStrategy: MergeStrategy.REPLACE,
},
},
},
},
},
} as const satisfies SettingsSchema;