mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 14:10:37 -07:00
feat(agent): enable agent skills by default (#16736)
This commit is contained in:
@@ -285,16 +285,15 @@ export async function parseArguments(
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (settings.experimental.extensionManagement) {
|
if (settings.experimental?.extensionManagement) {
|
||||||
yargsInstance.command(extensionsCommand);
|
yargsInstance.command(extensionsCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.experimental.skills) {
|
if (settings.experimental?.skills || (settings.skills?.enabled ?? true)) {
|
||||||
yargsInstance.command(skillsCommand);
|
yargsInstance.command(skillsCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register hooks command if hooks are enabled
|
// Register hooks command if hooks are enabled
|
||||||
if (settings.tools.enableHooks) {
|
if (settings.tools?.enableHooks) {
|
||||||
yargsInstance.command(hooksCommand);
|
yargsInstance.command(hooksCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -722,7 +721,8 @@ export async function loadCliConfig(
|
|||||||
enableExtensionReloading: settings.experimental?.extensionReloading,
|
enableExtensionReloading: settings.experimental?.extensionReloading,
|
||||||
enableAgents: settings.experimental?.enableAgents,
|
enableAgents: settings.experimental?.enableAgents,
|
||||||
plan: settings.experimental?.plan,
|
plan: settings.experimental?.plan,
|
||||||
skillsSupport: settings.experimental?.skills,
|
skillsSupport:
|
||||||
|
settings.experimental?.skills || (settings.skills?.enabled ?? true),
|
||||||
disabledSkills: settings.skills?.disabled,
|
disabledSkills: settings.skills?.disabled,
|
||||||
experimentalJitContext: settings.experimental?.jitContext,
|
experimentalJitContext: settings.experimental?.jitContext,
|
||||||
noBrowser: !!process.env['NO_BROWSER'],
|
noBrowser: !!process.env['NO_BROWSER'],
|
||||||
|
|||||||
@@ -357,6 +357,17 @@ describe('SettingsSchema', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should have skills setting enabled by default', () => {
|
||||||
|
const setting = getSettingsSchema().skills.properties.enabled;
|
||||||
|
expect(setting).toBeDefined();
|
||||||
|
expect(setting.type).toBe('boolean');
|
||||||
|
expect(setting.category).toBe('Advanced');
|
||||||
|
expect(setting.default).toBe(true);
|
||||||
|
expect(setting.requiresRestart).toBe(true);
|
||||||
|
expect(setting.showInDialog).toBe(true);
|
||||||
|
expect(setting.description).toBe('Enable Agent Skills.');
|
||||||
|
});
|
||||||
|
|
||||||
it('should have plan setting in schema', () => {
|
it('should have plan setting in schema', () => {
|
||||||
const setting = getSettingsSchema().experimental.properties.plan;
|
const setting = getSettingsSchema().experimental.properties.plan;
|
||||||
expect(setting).toBeDefined();
|
expect(setting).toBeDefined();
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ export interface SettingDefinition {
|
|||||||
key?: string;
|
key?: string;
|
||||||
properties?: SettingsSchema;
|
properties?: SettingsSchema;
|
||||||
showInDialog?: boolean;
|
showInDialog?: boolean;
|
||||||
|
ignoreInDocs?: boolean;
|
||||||
mergeStrategy?: MergeStrategy;
|
mergeStrategy?: MergeStrategy;
|
||||||
/** Enum type options */
|
/** Enum type options */
|
||||||
options?: readonly SettingEnumOption[];
|
options?: readonly SettingEnumOption[];
|
||||||
@@ -1597,6 +1598,16 @@ const SETTINGS_SCHEMA = {
|
|||||||
description: 'Settings for agent skills.',
|
description: 'Settings for agent skills.',
|
||||||
showInDialog: false,
|
showInDialog: false,
|
||||||
properties: {
|
properties: {
|
||||||
|
enabled: {
|
||||||
|
type: 'boolean',
|
||||||
|
label: 'Enable Agent Skills',
|
||||||
|
category: 'Advanced',
|
||||||
|
requiresRestart: true,
|
||||||
|
default: true,
|
||||||
|
description: 'Enable Agent Skills.',
|
||||||
|
showInDialog: true,
|
||||||
|
ignoreInDocs: true,
|
||||||
|
},
|
||||||
disabled: {
|
disabled: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
label: 'Disabled Skills',
|
label: 'Disabled Skills',
|
||||||
|
|||||||
158
packages/cli/src/config/skills-backward-compatibility.test.ts
Normal file
158
packages/cli/src/config/skills-backward-compatibility.test.ts
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||||
|
import { loadCliConfig, parseArguments } from './config.js';
|
||||||
|
import * as trustedFolders from './trustedFolders.js';
|
||||||
|
import { loadServerHierarchicalMemory } from '@google/gemini-cli-core';
|
||||||
|
import { type Settings, createTestMergedSettings } from './settings.js';
|
||||||
|
|
||||||
|
vi.mock('./trustedFolders.js');
|
||||||
|
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||||
|
const actual =
|
||||||
|
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
loadServerHierarchicalMemory: vi.fn(),
|
||||||
|
getPty: vi.fn().mockResolvedValue({ name: 'test-pty' }),
|
||||||
|
getVersion: vi.fn().mockResolvedValue('0.0.0-test'),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Agent Skills Backward Compatibility', () => {
|
||||||
|
const originalArgv = process.argv;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.resetAllMocks();
|
||||||
|
vi.mocked(trustedFolders.isWorkspaceTrusted).mockReturnValue({
|
||||||
|
isTrusted: true,
|
||||||
|
} as unknown as trustedFolders.TrustResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
process.argv = originalArgv;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('loadCliConfig', () => {
|
||||||
|
it('should default skillsSupport to true when no settings are present', async () => {
|
||||||
|
vi.mocked(loadServerHierarchicalMemory).mockResolvedValue({
|
||||||
|
memoryContent: '',
|
||||||
|
fileCount: 0,
|
||||||
|
filePaths: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
process.argv = ['node', 'gemini'];
|
||||||
|
const settings = createTestMergedSettings({});
|
||||||
|
const config = await loadCliConfig(
|
||||||
|
settings,
|
||||||
|
'test-session',
|
||||||
|
await parseArguments(settings),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
config as unknown as { isSkillsSupportEnabled: () => boolean }
|
||||||
|
).isSkillsSupportEnabled(),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should prioritize skills.enabled=false from settings', async () => {
|
||||||
|
vi.mocked(loadServerHierarchicalMemory).mockResolvedValue({
|
||||||
|
memoryContent: '',
|
||||||
|
fileCount: 0,
|
||||||
|
filePaths: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const settings = createTestMergedSettings({
|
||||||
|
skills: { enabled: false },
|
||||||
|
} as unknown as Settings);
|
||||||
|
|
||||||
|
process.argv = ['node', 'gemini'];
|
||||||
|
const config = await loadCliConfig(
|
||||||
|
settings,
|
||||||
|
'test-session',
|
||||||
|
await parseArguments(settings),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
config as unknown as { isSkillsSupportEnabled: () => boolean }
|
||||||
|
).isSkillsSupportEnabled(),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support legacy experimental.skills=true from settings', async () => {
|
||||||
|
vi.mocked(loadServerHierarchicalMemory).mockResolvedValue({
|
||||||
|
memoryContent: '',
|
||||||
|
fileCount: 0,
|
||||||
|
filePaths: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const settings = createTestMergedSettings({
|
||||||
|
experimental: { skills: true },
|
||||||
|
} as unknown as Settings);
|
||||||
|
|
||||||
|
process.argv = ['node', 'gemini'];
|
||||||
|
const config = await loadCliConfig(
|
||||||
|
settings,
|
||||||
|
'test-session',
|
||||||
|
await parseArguments(settings),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
config as unknown as { isSkillsSupportEnabled: () => boolean }
|
||||||
|
).isSkillsSupportEnabled(),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should prioritize legacy experimental.skills=true over new skills.enabled=false', async () => {
|
||||||
|
vi.mocked(loadServerHierarchicalMemory).mockResolvedValue({
|
||||||
|
memoryContent: '',
|
||||||
|
fileCount: 0,
|
||||||
|
filePaths: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const settings = createTestMergedSettings({
|
||||||
|
skills: { enabled: false },
|
||||||
|
experimental: { skills: true },
|
||||||
|
} as unknown as Settings);
|
||||||
|
|
||||||
|
process.argv = ['node', 'gemini'];
|
||||||
|
const config = await loadCliConfig(
|
||||||
|
settings,
|
||||||
|
'test-session',
|
||||||
|
await parseArguments(settings),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
config as unknown as { isSkillsSupportEnabled: () => boolean }
|
||||||
|
).isSkillsSupportEnabled(),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should still be enabled by default if legacy experimental.skills is false (since new default is true)', async () => {
|
||||||
|
vi.mocked(loadServerHierarchicalMemory).mockResolvedValue({
|
||||||
|
memoryContent: '',
|
||||||
|
fileCount: 0,
|
||||||
|
filePaths: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const settings = createTestMergedSettings({
|
||||||
|
experimental: { skills: false },
|
||||||
|
} as unknown as Settings);
|
||||||
|
|
||||||
|
process.argv = ['node', 'gemini'];
|
||||||
|
const config = await loadCliConfig(
|
||||||
|
settings,
|
||||||
|
'test-session',
|
||||||
|
await parseArguments(settings),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
config as unknown as { isSkillsSupportEnabled: () => boolean }
|
||||||
|
).isSkillsSupportEnabled(),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -10,7 +10,12 @@ import { MessageType, type HistoryItemSkillsList } from '../types.js';
|
|||||||
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
|
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
|
||||||
import type { CommandContext } from './types.js';
|
import type { CommandContext } from './types.js';
|
||||||
import type { Config, SkillDefinition } from '@google/gemini-cli-core';
|
import type { Config, SkillDefinition } from '@google/gemini-cli-core';
|
||||||
import { SettingScope, type LoadedSettings } from '../../config/settings.js';
|
import {
|
||||||
|
SettingScope,
|
||||||
|
type LoadedSettings,
|
||||||
|
createTestMergedSettings,
|
||||||
|
type MergedSettings,
|
||||||
|
} from '../../config/settings.js';
|
||||||
|
|
||||||
vi.mock('../../config/settings.js', async (importOriginal) => {
|
vi.mock('../../config/settings.js', async (importOriginal) => {
|
||||||
const actual =
|
const actual =
|
||||||
@@ -55,7 +60,7 @@ describe('skillsCommand', () => {
|
|||||||
}),
|
}),
|
||||||
} as unknown as Config,
|
} as unknown as Config,
|
||||||
settings: {
|
settings: {
|
||||||
merged: { skills: { disabled: [] } },
|
merged: createTestMergedSettings({ skills: { disabled: [] } }),
|
||||||
workspace: { path: '/workspace' },
|
workspace: { path: '/workspace' },
|
||||||
setValue: vi.fn(),
|
setValue: vi.fn(),
|
||||||
} as unknown as LoadedSettings,
|
} as unknown as LoadedSettings,
|
||||||
@@ -181,7 +186,11 @@ describe('skillsCommand', () => {
|
|||||||
|
|
||||||
describe('disable/enable', () => {
|
describe('disable/enable', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
context.services.settings.merged.skills = { disabled: [] };
|
(
|
||||||
|
context.services.settings as unknown as { merged: MergedSettings }
|
||||||
|
).merged = createTestMergedSettings({
|
||||||
|
skills: { enabled: true, disabled: [] },
|
||||||
|
});
|
||||||
(
|
(
|
||||||
context.services.settings as unknown as { workspace: { path: string } }
|
context.services.settings as unknown as { workspace: { path: string } }
|
||||||
).workspace = {
|
).workspace = {
|
||||||
@@ -234,7 +243,14 @@ describe('skillsCommand', () => {
|
|||||||
const enableCmd = skillsCommand.subCommands!.find(
|
const enableCmd = skillsCommand.subCommands!.find(
|
||||||
(s) => s.name === 'enable',
|
(s) => s.name === 'enable',
|
||||||
)!;
|
)!;
|
||||||
context.services.settings.merged.skills = { disabled: ['skill1'] };
|
(
|
||||||
|
context.services.settings as unknown as { merged: MergedSettings }
|
||||||
|
).merged = createTestMergedSettings({
|
||||||
|
skills: {
|
||||||
|
enabled: true,
|
||||||
|
disabled: ['skill1'],
|
||||||
|
},
|
||||||
|
});
|
||||||
(
|
(
|
||||||
context.services.settings as unknown as {
|
context.services.settings as unknown as {
|
||||||
workspace: { settings: { skills: { disabled: string[] } } };
|
workspace: { settings: { skills: { disabled: string[] } } };
|
||||||
|
|||||||
@@ -1537,6 +1537,13 @@
|
|||||||
"default": {},
|
"default": {},
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"title": "Enable Agent Skills",
|
||||||
|
"description": "Enable Agent Skills.",
|
||||||
|
"markdownDescription": "Enable Agent Skills.\n\n- Category: `Advanced`\n- Requires restart: `yes`\n- Default: `true`",
|
||||||
|
"default": true,
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"disabled": {
|
"disabled": {
|
||||||
"title": "Disabled Skills",
|
"title": "Disabled Skills",
|
||||||
"description": "List of disabled skills.",
|
"description": "List of disabled skills.",
|
||||||
|
|||||||
@@ -133,6 +133,10 @@ function collectEntries(
|
|||||||
definition.properties &&
|
definition.properties &&
|
||||||
Object.keys(definition.properties).length > 0;
|
Object.keys(definition.properties).length > 0;
|
||||||
|
|
||||||
|
if (definition.ignoreInDocs) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!hasChildren && (options.includeAll || definition.showInDialog)) {
|
if (!hasChildren && (options.includeAll || definition.showInDialog)) {
|
||||||
if (!sections.has(sectionKey)) {
|
if (!sections.has(sectionKey)) {
|
||||||
sections.set(sectionKey, []);
|
sections.set(sectionKey, []);
|
||||||
|
|||||||
Reference in New Issue
Block a user