mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-29 22:44:45 -07:00
Support for Built-in Agent Skills (#16045)
This commit is contained in:
@@ -25,6 +25,8 @@ export interface SkillDefinition {
|
||||
body: string;
|
||||
/** Whether the skill is currently disabled. */
|
||||
disabled?: boolean;
|
||||
/** Whether the skill is a built-in skill. */
|
||||
isBuiltin?: boolean;
|
||||
}
|
||||
|
||||
const FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)/;
|
||||
|
||||
@@ -163,4 +163,45 @@ description: desc1
|
||||
expect(service.getAllSkills()).toHaveLength(1);
|
||||
expect(service.getAllSkills()[0].disabled).toBe(true);
|
||||
});
|
||||
|
||||
it('should filter built-in skills in getDisplayableSkills', async () => {
|
||||
const service = new SkillManager();
|
||||
|
||||
// @ts-expect-error accessing private property for testing
|
||||
service.skills = [
|
||||
{
|
||||
name: 'regular-skill',
|
||||
description: 'regular',
|
||||
location: 'loc1',
|
||||
body: 'body',
|
||||
isBuiltin: false,
|
||||
},
|
||||
{
|
||||
name: 'builtin-skill',
|
||||
description: 'builtin',
|
||||
location: 'loc2',
|
||||
body: 'body',
|
||||
isBuiltin: true,
|
||||
},
|
||||
{
|
||||
name: 'disabled-builtin',
|
||||
description: 'disabled builtin',
|
||||
location: 'loc3',
|
||||
body: 'body',
|
||||
isBuiltin: true,
|
||||
disabled: true,
|
||||
},
|
||||
];
|
||||
|
||||
const displayable = service.getDisplayableSkills();
|
||||
expect(displayable).toHaveLength(1);
|
||||
expect(displayable[0].name).toBe('regular-skill');
|
||||
|
||||
const all = service.getAllSkills();
|
||||
expect(all).toHaveLength(3);
|
||||
|
||||
const enabled = service.getSkills();
|
||||
expect(enabled).toHaveLength(2);
|
||||
expect(enabled.map((s) => s.name)).toContain('builtin-skill');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -31,24 +31,36 @@ export class SkillManager {
|
||||
): Promise<void> {
|
||||
this.clearSkills();
|
||||
|
||||
// 1. Extension skills (lowest precedence)
|
||||
// 1. Built-in skills (lowest precedence)
|
||||
await this.discoverBuiltinSkills();
|
||||
|
||||
// 2. Extension skills
|
||||
for (const extension of extensions) {
|
||||
if (extension.isActive && extension.skills) {
|
||||
this.addSkillsWithPrecedence(extension.skills);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. User skills
|
||||
// 3. User skills
|
||||
const userSkills = await loadSkillsFromDir(Storage.getUserSkillsDir());
|
||||
this.addSkillsWithPrecedence(userSkills);
|
||||
|
||||
// 3. Project skills (highest precedence)
|
||||
// 4. Project skills (highest precedence)
|
||||
const projectSkills = await loadSkillsFromDir(
|
||||
storage.getProjectSkillsDir(),
|
||||
);
|
||||
this.addSkillsWithPrecedence(projectSkills);
|
||||
}
|
||||
|
||||
/**
|
||||
* Discovers built-in skills.
|
||||
*/
|
||||
private async discoverBuiltinSkills(): Promise<void> {
|
||||
// Built-in skills can be added here.
|
||||
// For now, this is a placeholder for where built-in skills will be loaded from.
|
||||
// They could be loaded from a specific directory within the package.
|
||||
}
|
||||
|
||||
private addSkillsWithPrecedence(newSkills: SkillDefinition[]): void {
|
||||
const skillMap = new Map<string, SkillDefinition>();
|
||||
for (const skill of [...this.skills, ...newSkills]) {
|
||||
@@ -64,6 +76,14 @@ export class SkillManager {
|
||||
return this.skills.filter((s) => !s.disabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of enabled discovered skills that should be displayed in the UI.
|
||||
* This excludes built-in skills.
|
||||
*/
|
||||
getDisplayableSkills(): SkillDefinition[] {
|
||||
return this.skills.filter((s) => !s.disabled && !s.isBuiltin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all discovered skills, including disabled ones.
|
||||
*/
|
||||
@@ -82,8 +102,11 @@ export class SkillManager {
|
||||
* Sets the list of disabled skill names.
|
||||
*/
|
||||
setDisabledSkills(disabledNames: string[]): void {
|
||||
const lowercaseDisabledNames = disabledNames.map((n) => n.toLowerCase());
|
||||
for (const skill of this.skills) {
|
||||
skill.disabled = disabledNames.includes(skill.name);
|
||||
skill.disabled = lowercaseDisabledNames.includes(
|
||||
skill.name.toLowerCase(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +114,10 @@ export class SkillManager {
|
||||
* Reads the full content (metadata + body) of a skill by name.
|
||||
*/
|
||||
getSkill(name: string): SkillDefinition | null {
|
||||
return this.skills.find((s) => s.name === name) ?? null;
|
||||
const lowercaseName = name.toLowerCase();
|
||||
return (
|
||||
this.skills.find((s) => s.name.toLowerCase() === lowercaseName) ?? null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -76,6 +76,34 @@ describe('ActivateSkillTool', () => {
|
||||
expect(details.prompt).toContain('Mock folder structure');
|
||||
});
|
||||
|
||||
it('should skip confirmation for built-in skills', async () => {
|
||||
const builtinSkill = {
|
||||
name: 'builtin-skill',
|
||||
description: 'A built-in skill',
|
||||
location: '/path/to/builtin/SKILL.md',
|
||||
isBuiltin: true,
|
||||
body: 'Built-in instructions',
|
||||
};
|
||||
vi.mocked(mockConfig.getSkillManager().getSkill).mockReturnValue(
|
||||
builtinSkill,
|
||||
);
|
||||
vi.mocked(mockConfig.getSkillManager().getSkills).mockReturnValue([
|
||||
builtinSkill,
|
||||
]);
|
||||
|
||||
const params = { name: 'builtin-skill' };
|
||||
const toolWithBuiltin = new ActivateSkillTool(mockConfig, mockMessageBus);
|
||||
const invocation = toolWithBuiltin.build(params);
|
||||
|
||||
const details = await (
|
||||
invocation as unknown as {
|
||||
getConfirmationDetails: (signal: AbortSignal) => Promise<unknown>;
|
||||
}
|
||||
).getConfirmationDetails(new AbortController().signal);
|
||||
|
||||
expect(details).toBe(false);
|
||||
});
|
||||
|
||||
it('should activate a valid skill and return its content in XML tags', async () => {
|
||||
const params = { name: 'test-skill' };
|
||||
const invocation = tool.build(params);
|
||||
|
||||
@@ -80,6 +80,10 @@ class ActivateSkillToolInvocation extends BaseToolInvocation<
|
||||
return false;
|
||||
}
|
||||
|
||||
if (skill.isBuiltin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const folderStructure = await this.getOrFetchFolderStructure(
|
||||
skill.location,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user