mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-26 21:14:35 -07:00
feat: enable subagents (#22386)
This commit is contained in:
@@ -7,20 +7,14 @@ the main agent's context or toolset.
|
|||||||
|
|
||||||
> **Note: Subagents are currently an experimental feature.**
|
> **Note: Subagents are currently an experimental feature.**
|
||||||
>
|
>
|
||||||
> To use custom subagents, you must explicitly enable them in your
|
> To use custom subagents, you must ensure they are enabled in your
|
||||||
> `settings.json`:
|
> `settings.json` (enabled by default):
|
||||||
>
|
>
|
||||||
> ```json
|
> ```json
|
||||||
> {
|
> {
|
||||||
> "experimental": { "enableAgents": true }
|
> "experimental": { "enableAgents": true }
|
||||||
> }
|
> }
|
||||||
> ```
|
> ```
|
||||||
>
|
|
||||||
> **Warning:** Subagents currently operate in
|
|
||||||
> ["YOLO mode"](../reference/configuration.md#command-line-arguments), meaning
|
|
||||||
> they may execute tools without individual user confirmation for each step.
|
|
||||||
> Proceed with caution when defining agents with powerful tools like
|
|
||||||
> `run_shell_command` or `write_file`.
|
|
||||||
|
|
||||||
## What are subagents?
|
## What are subagents?
|
||||||
|
|
||||||
|
|||||||
@@ -1158,9 +1158,8 @@ their corresponding top-level category object in your `settings.json` file.
|
|||||||
- **Requires restart:** Yes
|
- **Requires restart:** Yes
|
||||||
|
|
||||||
- **`experimental.enableAgents`** (boolean):
|
- **`experimental.enableAgents`** (boolean):
|
||||||
- **Description:** Enable local and remote subagents. Warning: Experimental
|
- **Description:** Enable local and remote subagents.
|
||||||
feature, uses YOLO mode for subagents
|
- **Default:** `true`
|
||||||
- **Default:** `false`
|
|
||||||
- **Requires restart:** Yes
|
- **Requires restart:** Yes
|
||||||
|
|
||||||
- **`experimental.extensionManagement`** (boolean):
|
- **`experimental.extensionManagement`** (boolean):
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ describe('handleInstall', () => {
|
|||||||
mcps: [],
|
mcps: [],
|
||||||
hooks: [],
|
hooks: [],
|
||||||
skills: [],
|
skills: [],
|
||||||
|
agents: [],
|
||||||
settings: [],
|
settings: [],
|
||||||
securityWarnings: [],
|
securityWarnings: [],
|
||||||
discoveryErrors: [],
|
discoveryErrors: [],
|
||||||
@@ -379,6 +380,7 @@ describe('handleInstall', () => {
|
|||||||
mcps: [],
|
mcps: [],
|
||||||
hooks: [],
|
hooks: [],
|
||||||
skills: ['cool-skill'],
|
skills: ['cool-skill'],
|
||||||
|
agents: ['cool-agent'],
|
||||||
settings: [],
|
settings: [],
|
||||||
securityWarnings: ['Security risk!'],
|
securityWarnings: ['Security risk!'],
|
||||||
discoveryErrors: ['Read error'],
|
discoveryErrors: ['Read error'],
|
||||||
@@ -408,6 +410,10 @@ describe('handleInstall', () => {
|
|||||||
expect.stringContaining('cool-skill'),
|
expect.stringContaining('cool-skill'),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
expect(mockPromptForConsentNonInteractive).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining('cool-agent'),
|
||||||
|
false,
|
||||||
|
);
|
||||||
expect(mockPromptForConsentNonInteractive).toHaveBeenCalledWith(
|
expect(mockPromptForConsentNonInteractive).toHaveBeenCalledWith(
|
||||||
expect.stringContaining('Security Warnings:'),
|
expect.stringContaining('Security Warnings:'),
|
||||||
false,
|
false,
|
||||||
|
|||||||
@@ -99,11 +99,15 @@ export async function handleInstall(args: InstallArgs) {
|
|||||||
if (hasDiscovery) {
|
if (hasDiscovery) {
|
||||||
promptLines.push(chalk.bold('This folder contains:'));
|
promptLines.push(chalk.bold('This folder contains:'));
|
||||||
const groups = [
|
const groups = [
|
||||||
{ label: 'Commands', items: discoveryResults.commands },
|
{ label: 'Commands', items: discoveryResults.commands ?? [] },
|
||||||
{ label: 'MCP Servers', items: discoveryResults.mcps },
|
{ label: 'MCP Servers', items: discoveryResults.mcps ?? [] },
|
||||||
{ label: 'Hooks', items: discoveryResults.hooks },
|
{ label: 'Hooks', items: discoveryResults.hooks ?? [] },
|
||||||
{ label: 'Skills', items: discoveryResults.skills },
|
{ label: 'Skills', items: discoveryResults.skills ?? [] },
|
||||||
{ label: 'Setting overrides', items: discoveryResults.settings },
|
{ label: 'Agents', items: discoveryResults.agents ?? [] },
|
||||||
|
{
|
||||||
|
label: 'Setting overrides',
|
||||||
|
items: discoveryResults.settings ?? [],
|
||||||
|
},
|
||||||
].filter((g) => g.items.length > 0);
|
].filter((g) => g.items.length > 0);
|
||||||
|
|
||||||
for (const group of groups) {
|
for (const group of groups) {
|
||||||
|
|||||||
@@ -400,12 +400,10 @@ describe('SettingsSchema', () => {
|
|||||||
expect(setting).toBeDefined();
|
expect(setting).toBeDefined();
|
||||||
expect(setting.type).toBe('boolean');
|
expect(setting.type).toBe('boolean');
|
||||||
expect(setting.category).toBe('Experimental');
|
expect(setting.category).toBe('Experimental');
|
||||||
expect(setting.default).toBe(false);
|
expect(setting.default).toBe(true);
|
||||||
expect(setting.requiresRestart).toBe(true);
|
expect(setting.requiresRestart).toBe(true);
|
||||||
expect(setting.showInDialog).toBe(false);
|
expect(setting.showInDialog).toBe(false);
|
||||||
expect(setting.description).toBe(
|
expect(setting.description).toBe('Enable local and remote subagents.');
|
||||||
'Enable local and remote subagents. Warning: Experimental feature, uses YOLO mode for subagents',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have skills setting enabled by default', () => {
|
it('should have skills setting enabled by default', () => {
|
||||||
|
|||||||
@@ -1838,9 +1838,8 @@ const SETTINGS_SCHEMA = {
|
|||||||
label: 'Enable Agents',
|
label: 'Enable Agents',
|
||||||
category: 'Experimental',
|
category: 'Experimental',
|
||||||
requiresRestart: true,
|
requiresRestart: true,
|
||||||
default: false,
|
default: true,
|
||||||
description:
|
description: 'Enable local and remote subagents.',
|
||||||
'Enable local and remote subagents. Warning: Experimental feature, uses YOLO mode for subagents',
|
|
||||||
showInDialog: false,
|
showInDialog: false,
|
||||||
},
|
},
|
||||||
extensionManagement: {
|
extensionManagement: {
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ describe('FolderTrustDialog', () => {
|
|||||||
mcps: Array.from({ length: 10 }, (_, i) => `mcp${i}`),
|
mcps: Array.from({ length: 10 }, (_, i) => `mcp${i}`),
|
||||||
hooks: Array.from({ length: 10 }, (_, i) => `hook${i}`),
|
hooks: Array.from({ length: 10 }, (_, i) => `hook${i}`),
|
||||||
skills: Array.from({ length: 10 }, (_, i) => `skill${i}`),
|
skills: Array.from({ length: 10 }, (_, i) => `skill${i}`),
|
||||||
|
agents: [],
|
||||||
settings: Array.from({ length: 10 }, (_, i) => `setting${i}`),
|
settings: Array.from({ length: 10 }, (_, i) => `setting${i}`),
|
||||||
discoveryErrors: [],
|
discoveryErrors: [],
|
||||||
securityWarnings: [],
|
securityWarnings: [],
|
||||||
@@ -95,6 +96,7 @@ describe('FolderTrustDialog', () => {
|
|||||||
mcps: [],
|
mcps: [],
|
||||||
hooks: [],
|
hooks: [],
|
||||||
skills: [],
|
skills: [],
|
||||||
|
agents: [],
|
||||||
settings: [],
|
settings: [],
|
||||||
discoveryErrors: [],
|
discoveryErrors: [],
|
||||||
securityWarnings: [],
|
securityWarnings: [],
|
||||||
@@ -125,6 +127,7 @@ describe('FolderTrustDialog', () => {
|
|||||||
mcps: [],
|
mcps: [],
|
||||||
hooks: [],
|
hooks: [],
|
||||||
skills: [],
|
skills: [],
|
||||||
|
agents: [],
|
||||||
settings: [],
|
settings: [],
|
||||||
discoveryErrors: [],
|
discoveryErrors: [],
|
||||||
securityWarnings: [],
|
securityWarnings: [],
|
||||||
@@ -152,6 +155,7 @@ describe('FolderTrustDialog', () => {
|
|||||||
mcps: [],
|
mcps: [],
|
||||||
hooks: [],
|
hooks: [],
|
||||||
skills: [],
|
skills: [],
|
||||||
|
agents: [],
|
||||||
settings: [],
|
settings: [],
|
||||||
discoveryErrors: [],
|
discoveryErrors: [],
|
||||||
securityWarnings: [],
|
securityWarnings: [],
|
||||||
@@ -332,6 +336,7 @@ describe('FolderTrustDialog', () => {
|
|||||||
mcps: ['mcp1'],
|
mcps: ['mcp1'],
|
||||||
hooks: ['hook1'],
|
hooks: ['hook1'],
|
||||||
skills: ['skill1'],
|
skills: ['skill1'],
|
||||||
|
agents: ['agent1'],
|
||||||
settings: ['general', 'ui'],
|
settings: ['general', 'ui'],
|
||||||
discoveryErrors: [],
|
discoveryErrors: [],
|
||||||
securityWarnings: [],
|
securityWarnings: [],
|
||||||
@@ -355,6 +360,8 @@ describe('FolderTrustDialog', () => {
|
|||||||
expect(lastFrame()).toContain('- hook1');
|
expect(lastFrame()).toContain('- hook1');
|
||||||
expect(lastFrame()).toContain('• Skills (1):');
|
expect(lastFrame()).toContain('• Skills (1):');
|
||||||
expect(lastFrame()).toContain('- skill1');
|
expect(lastFrame()).toContain('- skill1');
|
||||||
|
expect(lastFrame()).toContain('• Agents (1):');
|
||||||
|
expect(lastFrame()).toContain('- agent1');
|
||||||
expect(lastFrame()).toContain('• Setting overrides (2):');
|
expect(lastFrame()).toContain('• Setting overrides (2):');
|
||||||
expect(lastFrame()).toContain('- general');
|
expect(lastFrame()).toContain('- general');
|
||||||
expect(lastFrame()).toContain('- ui');
|
expect(lastFrame()).toContain('- ui');
|
||||||
@@ -367,6 +374,7 @@ describe('FolderTrustDialog', () => {
|
|||||||
mcps: [],
|
mcps: [],
|
||||||
hooks: [],
|
hooks: [],
|
||||||
skills: [],
|
skills: [],
|
||||||
|
agents: [],
|
||||||
settings: [],
|
settings: [],
|
||||||
discoveryErrors: [],
|
discoveryErrors: [],
|
||||||
securityWarnings: ['Dangerous setting detected!'],
|
securityWarnings: ['Dangerous setting detected!'],
|
||||||
@@ -390,6 +398,7 @@ describe('FolderTrustDialog', () => {
|
|||||||
mcps: [],
|
mcps: [],
|
||||||
hooks: [],
|
hooks: [],
|
||||||
skills: [],
|
skills: [],
|
||||||
|
agents: [],
|
||||||
settings: [],
|
settings: [],
|
||||||
discoveryErrors: ['Failed to load custom commands'],
|
discoveryErrors: ['Failed to load custom commands'],
|
||||||
securityWarnings: [],
|
securityWarnings: [],
|
||||||
@@ -413,6 +422,7 @@ describe('FolderTrustDialog', () => {
|
|||||||
mcps: [],
|
mcps: [],
|
||||||
hooks: [],
|
hooks: [],
|
||||||
skills: [],
|
skills: [],
|
||||||
|
agents: [],
|
||||||
settings: [],
|
settings: [],
|
||||||
discoveryErrors: [],
|
discoveryErrors: [],
|
||||||
securityWarnings: [],
|
securityWarnings: [],
|
||||||
@@ -446,6 +456,7 @@ describe('FolderTrustDialog', () => {
|
|||||||
mcps: [`${ansiRed}mcp-with-ansi${ansiReset}`],
|
mcps: [`${ansiRed}mcp-with-ansi${ansiReset}`],
|
||||||
hooks: [`${ansiRed}hook-with-ansi${ansiReset}`],
|
hooks: [`${ansiRed}hook-with-ansi${ansiReset}`],
|
||||||
skills: [`${ansiRed}skill-with-ansi${ansiReset}`],
|
skills: [`${ansiRed}skill-with-ansi${ansiReset}`],
|
||||||
|
agents: [],
|
||||||
settings: [`${ansiRed}setting-with-ansi${ansiReset}`],
|
settings: [`${ansiRed}setting-with-ansi${ansiReset}`],
|
||||||
discoveryErrors: [`${ansiRed}error-with-ansi${ansiReset}`],
|
discoveryErrors: [`${ansiRed}error-with-ansi${ansiReset}`],
|
||||||
securityWarnings: [`${ansiRed}warning-with-ansi${ansiReset}`],
|
securityWarnings: [`${ansiRed}warning-with-ansi${ansiReset}`],
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ export const FolderTrustDialog: React.FC<FolderTrustDialogProps> = ({
|
|||||||
{ label: 'MCP Servers', items: discoveryResults?.mcps ?? [] },
|
{ label: 'MCP Servers', items: discoveryResults?.mcps ?? [] },
|
||||||
{ label: 'Hooks', items: discoveryResults?.hooks ?? [] },
|
{ label: 'Hooks', items: discoveryResults?.hooks ?? [] },
|
||||||
{ label: 'Skills', items: discoveryResults?.skills ?? [] },
|
{ label: 'Skills', items: discoveryResults?.skills ?? [] },
|
||||||
|
{ label: 'Agents', items: discoveryResults?.agents ?? [] },
|
||||||
{ label: 'Setting overrides', items: discoveryResults?.settings ?? [] },
|
{ label: 'Setting overrides', items: discoveryResults?.settings ?? [] },
|
||||||
].filter((g) => g.items.length > 0);
|
].filter((g) => g.items.length > 0);
|
||||||
|
|
||||||
|
|||||||
@@ -1246,7 +1246,7 @@ describe('Server Config (config.ts)', () => {
|
|||||||
const config = new Config(params);
|
const config = new Config(params);
|
||||||
|
|
||||||
const mockAgentDefinition = {
|
const mockAgentDefinition = {
|
||||||
name: 'codebase-investigator',
|
name: 'codebase_investigator',
|
||||||
description: 'Agent 1',
|
description: 'Agent 1',
|
||||||
instructions: 'Inst 1',
|
instructions: 'Inst 1',
|
||||||
};
|
};
|
||||||
@@ -1294,7 +1294,7 @@ describe('Server Config (config.ts)', () => {
|
|||||||
it('should register subagents as tools even when they are not in allowedTools', async () => {
|
it('should register subagents as tools even when they are not in allowedTools', async () => {
|
||||||
const params: ConfigParameters = {
|
const params: ConfigParameters = {
|
||||||
...baseParams,
|
...baseParams,
|
||||||
allowedTools: ['read_file'], // codebase-investigator is NOT here
|
allowedTools: ['read_file'], // codebase_investigator is NOT here
|
||||||
agents: {
|
agents: {
|
||||||
overrides: {
|
overrides: {
|
||||||
codebase_investigator: { enabled: true },
|
codebase_investigator: { enabled: true },
|
||||||
@@ -1304,7 +1304,7 @@ describe('Server Config (config.ts)', () => {
|
|||||||
const config = new Config(params);
|
const config = new Config(params);
|
||||||
|
|
||||||
const mockAgentDefinition = {
|
const mockAgentDefinition = {
|
||||||
name: 'codebase-investigator',
|
name: 'codebase_investigator',
|
||||||
description: 'Agent 1',
|
description: 'Agent 1',
|
||||||
instructions: 'Inst 1',
|
instructions: 'Inst 1',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -948,7 +948,7 @@ export class Config implements McpContext, AgentLoopContext {
|
|||||||
this.model = params.model;
|
this.model = params.model;
|
||||||
this.disableLoopDetection = params.disableLoopDetection ?? false;
|
this.disableLoopDetection = params.disableLoopDetection ?? false;
|
||||||
this._activeModel = params.model;
|
this._activeModel = params.model;
|
||||||
this.enableAgents = params.enableAgents ?? false;
|
this.enableAgents = params.enableAgents ?? true;
|
||||||
this.agents = params.agents ?? {};
|
this.agents = params.agents ?? {};
|
||||||
this.disableLLMCorrection = params.disableLLMCorrection ?? true;
|
this.disableLLMCorrection = params.disableLLMCorrection ?? true;
|
||||||
this.planEnabled = params.plan ?? true;
|
this.planEnabled = params.plan ?? true;
|
||||||
@@ -3147,22 +3147,23 @@ export class Config implements McpContext, AgentLoopContext {
|
|||||||
*/
|
*/
|
||||||
private registerSubAgentTools(registry: ToolRegistry): void {
|
private registerSubAgentTools(registry: ToolRegistry): void {
|
||||||
const agentsOverrides = this.getAgentsSettings().overrides ?? {};
|
const agentsOverrides = this.getAgentsSettings().overrides ?? {};
|
||||||
if (
|
const definitions = this.agentRegistry.getAllDefinitions();
|
||||||
this.isAgentsEnabled() ||
|
|
||||||
agentsOverrides['codebase_investigator']?.enabled !== false ||
|
|
||||||
agentsOverrides['cli_help']?.enabled !== false
|
|
||||||
) {
|
|
||||||
const definitions = this.agentRegistry.getAllDefinitions();
|
|
||||||
|
|
||||||
for (const definition of definitions) {
|
for (const definition of definitions) {
|
||||||
try {
|
try {
|
||||||
const tool = new SubagentTool(definition, this, this.messageBus);
|
if (
|
||||||
registry.registerTool(tool);
|
!this.isAgentsEnabled() ||
|
||||||
} catch (e: unknown) {
|
agentsOverrides[definition.name]?.enabled === false
|
||||||
debugLogger.warn(
|
) {
|
||||||
`Failed to register tool for agent ${definition.name}: ${getErrorMessage(e)}`,
|
continue;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tool = new SubagentTool(definition, this, this.messageBus);
|
||||||
|
registry.registerTool(tool);
|
||||||
|
} catch (e: unknown) {
|
||||||
|
debugLogger.warn(
|
||||||
|
`Failed to register tool for agent ${definition.name}: ${getErrorMessage(e)}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,11 @@ describe('FolderTrustDiscoveryService', () => {
|
|||||||
await fs.mkdir(path.join(skillsDir, 'test-skill'), { recursive: true });
|
await fs.mkdir(path.join(skillsDir, 'test-skill'), { recursive: true });
|
||||||
await fs.writeFile(path.join(skillsDir, 'test-skill', 'SKILL.md'), 'body');
|
await fs.writeFile(path.join(skillsDir, 'test-skill', 'SKILL.md'), 'body');
|
||||||
|
|
||||||
|
// Mock agents
|
||||||
|
const agentsDir = path.join(geminiDir, 'agents');
|
||||||
|
await fs.mkdir(agentsDir);
|
||||||
|
await fs.writeFile(path.join(agentsDir, 'test-agent.md'), 'body');
|
||||||
|
|
||||||
// Mock settings (MCPs, Hooks, and general settings)
|
// Mock settings (MCPs, Hooks, and general settings)
|
||||||
const settings = {
|
const settings = {
|
||||||
mcpServers: {
|
mcpServers: {
|
||||||
@@ -62,6 +67,7 @@ describe('FolderTrustDiscoveryService', () => {
|
|||||||
|
|
||||||
expect(results.commands).toContain('test-cmd');
|
expect(results.commands).toContain('test-cmd');
|
||||||
expect(results.skills).toContain('test-skill');
|
expect(results.skills).toContain('test-skill');
|
||||||
|
expect(results.agents).toContain('test-agent');
|
||||||
expect(results.mcps).toContain('test-mcp');
|
expect(results.mcps).toContain('test-mcp');
|
||||||
expect(results.hooks).toContain('test-hook');
|
expect(results.hooks).toContain('test-hook');
|
||||||
expect(results.settings).toContain('general');
|
expect(results.settings).toContain('general');
|
||||||
@@ -79,9 +85,6 @@ describe('FolderTrustDiscoveryService', () => {
|
|||||||
allowed: ['git'],
|
allowed: ['git'],
|
||||||
sandbox: false,
|
sandbox: false,
|
||||||
},
|
},
|
||||||
experimental: {
|
|
||||||
enableAgents: true,
|
|
||||||
},
|
|
||||||
security: {
|
security: {
|
||||||
folderTrust: {
|
folderTrust: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
@@ -98,9 +101,6 @@ describe('FolderTrustDiscoveryService', () => {
|
|||||||
expect(results.securityWarnings).toContain(
|
expect(results.securityWarnings).toContain(
|
||||||
'This project auto-approves certain tools (tools.allowed).',
|
'This project auto-approves certain tools (tools.allowed).',
|
||||||
);
|
);
|
||||||
expect(results.securityWarnings).toContain(
|
|
||||||
'This project enables autonomous agents (enableAgents).',
|
|
||||||
);
|
|
||||||
expect(results.securityWarnings).toContain(
|
expect(results.securityWarnings).toContain(
|
||||||
'This project attempts to disable folder trust (security.folderTrust.enabled).',
|
'This project attempts to disable folder trust (security.folderTrust.enabled).',
|
||||||
);
|
);
|
||||||
@@ -158,4 +158,20 @@ describe('FolderTrustDiscoveryService', () => {
|
|||||||
expect(results.discoveryErrors).toHaveLength(0);
|
expect(results.discoveryErrors).toHaveLength(0);
|
||||||
expect(results.settings).toHaveLength(0);
|
expect(results.settings).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should flag security warning for custom agents', async () => {
|
||||||
|
const geminiDir = path.join(tempDir, GEMINI_DIR);
|
||||||
|
await fs.mkdir(geminiDir, { recursive: true });
|
||||||
|
|
||||||
|
const agentsDir = path.join(geminiDir, 'agents');
|
||||||
|
await fs.mkdir(agentsDir);
|
||||||
|
await fs.writeFile(path.join(agentsDir, 'test-agent.md'), 'body');
|
||||||
|
|
||||||
|
const results = await FolderTrustDiscoveryService.discover(tempDir);
|
||||||
|
|
||||||
|
expect(results.agents).toContain('test-agent');
|
||||||
|
expect(results.securityWarnings).toContain(
|
||||||
|
'This project contains custom agents.',
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export interface FolderDiscoveryResults {
|
|||||||
mcps: string[];
|
mcps: string[];
|
||||||
hooks: string[];
|
hooks: string[];
|
||||||
skills: string[];
|
skills: string[];
|
||||||
|
agents: string[];
|
||||||
settings: string[];
|
settings: string[];
|
||||||
securityWarnings: string[];
|
securityWarnings: string[];
|
||||||
discoveryErrors: string[];
|
discoveryErrors: string[];
|
||||||
@@ -37,6 +38,7 @@ export class FolderTrustDiscoveryService {
|
|||||||
mcps: [],
|
mcps: [],
|
||||||
hooks: [],
|
hooks: [],
|
||||||
skills: [],
|
skills: [],
|
||||||
|
agents: [],
|
||||||
settings: [],
|
settings: [],
|
||||||
securityWarnings: [],
|
securityWarnings: [],
|
||||||
discoveryErrors: [],
|
discoveryErrors: [],
|
||||||
@@ -50,6 +52,7 @@ export class FolderTrustDiscoveryService {
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.discoverCommands(geminiDir, results),
|
this.discoverCommands(geminiDir, results),
|
||||||
this.discoverSkills(geminiDir, results),
|
this.discoverSkills(geminiDir, results),
|
||||||
|
this.discoverAgents(geminiDir, results),
|
||||||
this.discoverSettings(geminiDir, results),
|
this.discoverSettings(geminiDir, results),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -99,6 +102,34 @@ export class FolderTrustDiscoveryService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async discoverAgents(
|
||||||
|
geminiDir: string,
|
||||||
|
results: FolderDiscoveryResults,
|
||||||
|
) {
|
||||||
|
const agentsDir = path.join(geminiDir, 'agents');
|
||||||
|
if (await this.exists(agentsDir)) {
|
||||||
|
try {
|
||||||
|
const entries = await fs.readdir(agentsDir, { withFileTypes: true });
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (
|
||||||
|
entry.isFile() &&
|
||||||
|
entry.name.endsWith('.md') &&
|
||||||
|
!entry.name.startsWith('_')
|
||||||
|
) {
|
||||||
|
results.agents.push(path.basename(entry.name, '.md'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (results.agents.length > 0) {
|
||||||
|
results.securityWarnings.push('This project contains custom agents.');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
results.discoveryErrors.push(
|
||||||
|
`Failed to discover agents: ${e instanceof Error ? e.message : String(e)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static async discoverSettings(
|
private static async discoverSettings(
|
||||||
geminiDir: string,
|
geminiDir: string,
|
||||||
results: FolderDiscoveryResults,
|
results: FolderDiscoveryResults,
|
||||||
@@ -119,7 +150,7 @@ export class FolderTrustDiscoveryService {
|
|||||||
(key) => !['mcpServers', 'hooks', '$schema'].includes(key),
|
(key) => !['mcpServers', 'hooks', '$schema'].includes(key),
|
||||||
);
|
);
|
||||||
|
|
||||||
results.securityWarnings = this.collectSecurityWarnings(settings);
|
results.securityWarnings.push(...this.collectSecurityWarnings(settings));
|
||||||
|
|
||||||
const mcpServers = settings['mcpServers'];
|
const mcpServers = settings['mcpServers'];
|
||||||
if (this.isRecord(mcpServers)) {
|
if (this.isRecord(mcpServers)) {
|
||||||
@@ -159,10 +190,6 @@ export class FolderTrustDiscoveryService {
|
|||||||
? settings['tools']
|
? settings['tools']
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const experimental = this.isRecord(settings['experimental'])
|
|
||||||
? settings['experimental']
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const security = this.isRecord(settings['security'])
|
const security = this.isRecord(settings['security'])
|
||||||
? settings['security']
|
? settings['security']
|
||||||
: undefined;
|
: undefined;
|
||||||
@@ -179,10 +206,6 @@ export class FolderTrustDiscoveryService {
|
|||||||
condition: Array.isArray(allowedTools) && allowedTools.length > 0,
|
condition: Array.isArray(allowedTools) && allowedTools.length > 0,
|
||||||
message: 'This project auto-approves certain tools (tools.allowed).',
|
message: 'This project auto-approves certain tools (tools.allowed).',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
condition: experimental?.['enableAgents'] === true,
|
|
||||||
message: 'This project enables autonomous agents (enableAgents).',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
condition: folderTrust?.['enabled'] === false,
|
condition: folderTrust?.['enabled'] === false,
|
||||||
message:
|
message:
|
||||||
|
|||||||
@@ -1970,9 +1970,9 @@
|
|||||||
},
|
},
|
||||||
"enableAgents": {
|
"enableAgents": {
|
||||||
"title": "Enable Agents",
|
"title": "Enable Agents",
|
||||||
"description": "Enable local and remote subagents. Warning: Experimental feature, uses YOLO mode for subagents",
|
"description": "Enable local and remote subagents.",
|
||||||
"markdownDescription": "Enable local and remote subagents. Warning: Experimental feature, uses YOLO mode for subagents\n\n- Category: `Experimental`\n- Requires restart: `yes`\n- Default: `false`",
|
"markdownDescription": "Enable local and remote subagents.\n\n- Category: `Experimental`\n- Requires restart: `yes`\n- Default: `true`",
|
||||||
"default": false,
|
"default": true,
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"extensionManagement": {
|
"extensionManagement": {
|
||||||
|
|||||||
Reference in New Issue
Block a user