mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-16 16:21:27 -07:00
feat(admin): support admin-enforced settings for Agent Skills (#16406)
This commit is contained in:
@@ -2091,5 +2091,30 @@ describe('Config JIT Initialization', () => {
|
||||
|
||||
expect(skillManager.setDisabledSkills).toHaveBeenCalledWith([]);
|
||||
});
|
||||
|
||||
it('should update admin settings from onReload', async () => {
|
||||
const mockOnReload = vi.fn().mockResolvedValue({
|
||||
adminSkillsEnabled: false,
|
||||
});
|
||||
const params: ConfigParameters = {
|
||||
sessionId: 'test-session',
|
||||
targetDir: '/tmp/test',
|
||||
debugMode: false,
|
||||
model: 'test-model',
|
||||
cwd: '/tmp/test',
|
||||
skillsSupport: true,
|
||||
onReload: mockOnReload,
|
||||
};
|
||||
|
||||
config = new Config(params);
|
||||
await config.initialize();
|
||||
|
||||
const skillManager = config.getSkillManager();
|
||||
vi.spyOn(skillManager, 'setAdminSettings');
|
||||
|
||||
await config.reloadSkills();
|
||||
|
||||
expect(skillManager.setAdminSettings).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -377,13 +377,17 @@ export interface ConfigParameters {
|
||||
enableAgents?: boolean;
|
||||
skillsSupport?: boolean;
|
||||
disabledSkills?: string[];
|
||||
adminSkillsEnabled?: boolean;
|
||||
experimentalJitContext?: boolean;
|
||||
disableLLMCorrection?: boolean;
|
||||
onModelChange?: (model: string) => void;
|
||||
mcpEnabled?: boolean;
|
||||
extensionsEnabled?: boolean;
|
||||
agents?: AgentSettings;
|
||||
onReload?: () => Promise<{ disabledSkills?: string[] }>;
|
||||
onReload?: () => Promise<{
|
||||
disabledSkills?: string[];
|
||||
adminSkillsEnabled?: boolean;
|
||||
}>;
|
||||
}
|
||||
|
||||
export class Config {
|
||||
@@ -511,13 +515,17 @@ export class Config {
|
||||
private hookSystem?: HookSystem;
|
||||
private readonly onModelChange: ((model: string) => void) | undefined;
|
||||
private readonly onReload:
|
||||
| (() => Promise<{ disabledSkills?: string[] }>)
|
||||
| (() => Promise<{
|
||||
disabledSkills?: string[];
|
||||
adminSkillsEnabled?: boolean;
|
||||
}>)
|
||||
| undefined;
|
||||
|
||||
private readonly enableAgents: boolean;
|
||||
private readonly agents: AgentSettings;
|
||||
private readonly skillsSupport: boolean;
|
||||
private disabledSkills: string[];
|
||||
private readonly adminSkillsEnabled: boolean;
|
||||
|
||||
private readonly experimentalJitContext: boolean;
|
||||
private readonly disableLLMCorrection: boolean;
|
||||
@@ -594,6 +602,7 @@ export class Config {
|
||||
this.disableLLMCorrection = params.disableLLMCorrection ?? false;
|
||||
this.skillsSupport = params.skillsSupport ?? false;
|
||||
this.disabledSkills = params.disabledSkills ?? [];
|
||||
this.adminSkillsEnabled = params.adminSkillsEnabled ?? true;
|
||||
this.modelAvailabilityService = new ModelAvailabilityService();
|
||||
this.previewFeatures = params.previewFeatures ?? undefined;
|
||||
this.experimentalJitContext = params.experimentalJitContext ?? false;
|
||||
@@ -777,20 +786,22 @@ export class Config {
|
||||
]);
|
||||
initMcpHandle?.end();
|
||||
|
||||
// Discover skills if enabled
|
||||
if (this.skillsSupport) {
|
||||
await this.getSkillManager().discoverSkills(
|
||||
this.storage,
|
||||
this.getExtensions(),
|
||||
);
|
||||
this.getSkillManager().setDisabledSkills(this.disabledSkills);
|
||||
|
||||
// Re-register ActivateSkillTool to update its schema with the discovered enabled skill enums
|
||||
if (this.getSkillManager().getSkills().length > 0) {
|
||||
this.getToolRegistry().unregisterTool(ActivateSkillTool.Name);
|
||||
this.getToolRegistry().registerTool(
|
||||
new ActivateSkillTool(this, this.messageBus),
|
||||
this.getSkillManager().setAdminSettings(this.adminSkillsEnabled);
|
||||
if (this.adminSkillsEnabled) {
|
||||
await this.getSkillManager().discoverSkills(
|
||||
this.storage,
|
||||
this.getExtensions(),
|
||||
);
|
||||
this.getSkillManager().setDisabledSkills(this.disabledSkills);
|
||||
|
||||
// Re-register ActivateSkillTool to update its schema with the discovered enabled skill enums
|
||||
if (this.getSkillManager().getSkills().length > 0) {
|
||||
this.getToolRegistry().unregisterTool(ActivateSkillTool.Name);
|
||||
this.getToolRegistry().registerTool(
|
||||
new ActivateSkillTool(this, this.messageBus),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1593,21 +1604,29 @@ export class Config {
|
||||
if (this.onReload) {
|
||||
const refreshed = await this.onReload();
|
||||
this.disabledSkills = refreshed.disabledSkills ?? [];
|
||||
this.getSkillManager().setAdminSettings(
|
||||
refreshed.adminSkillsEnabled ?? this.adminSkillsEnabled,
|
||||
);
|
||||
}
|
||||
|
||||
await this.getSkillManager().discoverSkills(
|
||||
this.storage,
|
||||
this.getExtensions(),
|
||||
);
|
||||
this.getSkillManager().setDisabledSkills(this.disabledSkills);
|
||||
|
||||
// Re-register ActivateSkillTool to update its schema with the newly discovered skills
|
||||
if (this.getSkillManager().getSkills().length > 0) {
|
||||
this.getToolRegistry().unregisterTool(ActivateSkillTool.Name);
|
||||
this.getToolRegistry().registerTool(
|
||||
new ActivateSkillTool(this, this.messageBus),
|
||||
if (this.getSkillManager().isAdminEnabled()) {
|
||||
await this.getSkillManager().discoverSkills(
|
||||
this.storage,
|
||||
this.getExtensions(),
|
||||
);
|
||||
this.getSkillManager().setDisabledSkills(this.disabledSkills);
|
||||
|
||||
// Re-register ActivateSkillTool to update its schema with the newly discovered skills
|
||||
if (this.getSkillManager().getSkills().length > 0) {
|
||||
this.getToolRegistry().unregisterTool(ActivateSkillTool.Name);
|
||||
this.getToolRegistry().registerTool(
|
||||
new ActivateSkillTool(this, this.messageBus),
|
||||
);
|
||||
} else {
|
||||
this.getToolRegistry().unregisterTool(ActivateSkillTool.Name);
|
||||
}
|
||||
} else {
|
||||
this.getSkillManager().clearSkills();
|
||||
this.getToolRegistry().unregisterTool(ActivateSkillTool.Name);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,6 @@ import nodePath from 'node:path';
|
||||
import type { PolicySettings } from './types.js';
|
||||
import { ApprovalMode, PolicyDecision, InProcessCheckerType } from './types.js';
|
||||
|
||||
import { Storage } from '../config/storage.js';
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.restoreAllMocks();
|
||||
@@ -20,7 +18,9 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe('createPolicyEngineConfig', () => {
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
const { Storage } = await import('../config/storage.js');
|
||||
// Mock Storage to avoid picking up real user/system policies from the host environment
|
||||
vi.spyOn(Storage, 'getUserPoliciesDir').mockReturnValue(
|
||||
'/non/existent/user/policies',
|
||||
|
||||
@@ -189,7 +189,7 @@ description: project-desc
|
||||
name: skill1
|
||||
description: desc1
|
||||
---
|
||||
`,
|
||||
body1`,
|
||||
);
|
||||
|
||||
const storage = new Storage('/dummy');
|
||||
@@ -247,4 +247,20 @@ description: desc1
|
||||
expect(enabled).toHaveLength(2);
|
||||
expect(enabled.map((s) => s.name)).toContain('builtin-skill');
|
||||
});
|
||||
|
||||
it('should maintain admin settings state', async () => {
|
||||
const service = new SkillManager();
|
||||
|
||||
// Case 1: Enabled by admin
|
||||
|
||||
service.setAdminSettings(true);
|
||||
|
||||
expect(service.isAdminEnabled()).toBe(true);
|
||||
|
||||
// Case 2: Disabled by admin
|
||||
|
||||
service.setAdminSettings(false);
|
||||
|
||||
expect(service.isAdminEnabled()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ export { type SkillDefinition };
|
||||
export class SkillManager {
|
||||
private skills: SkillDefinition[] = [];
|
||||
private activeSkillNames: Set<string> = new Set();
|
||||
private adminSkillsEnabled = true;
|
||||
|
||||
/**
|
||||
* Clears all discovered skills.
|
||||
@@ -23,6 +24,20 @@ export class SkillManager {
|
||||
this.skills = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets administrative settings for skills.
|
||||
*/
|
||||
setAdminSettings(enabled: boolean): void {
|
||||
this.adminSkillsEnabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if skills are enabled by the admin.
|
||||
*/
|
||||
isAdminEnabled(): boolean {
|
||||
return this.adminSkillsEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discovers skills from standard user and project locations, as well as extensions.
|
||||
* Precedence: Extensions (lowest) -> User -> Project (highest).
|
||||
|
||||
@@ -278,7 +278,7 @@ describe('WorkspaceContext with real filesystem', () => {
|
||||
// handle it gracefully and return false.
|
||||
expect(workspaceContext.isPathWithinWorkspace(linkA)).toBe(false);
|
||||
expect(workspaceContext.isPathWithinWorkspace(linkB)).toBe(false);
|
||||
});
|
||||
}, 30000);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user