mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-22 11:04:42 -07:00
feat(core): move subagent policy to declarative TOML and add tool annotations
This commit is contained in:
@@ -23,7 +23,6 @@ import {
|
|||||||
type ModelConfig,
|
type ModelConfig,
|
||||||
ModelConfigService,
|
ModelConfigService,
|
||||||
} from '../services/modelConfigService.js';
|
} from '../services/modelConfigService.js';
|
||||||
import { PolicyDecision, PRIORITY_SUBAGENT_TOOL } from '../policy/types.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the model config alias for a given agent definition.
|
* Returns the model config alias for a given agent definition.
|
||||||
@@ -315,39 +314,6 @@ export class AgentRegistry {
|
|||||||
this.agents.set(mergedDefinition.name, mergedDefinition);
|
this.agents.set(mergedDefinition.name, mergedDefinition);
|
||||||
|
|
||||||
this.registerModelConfigs(mergedDefinition);
|
this.registerModelConfigs(mergedDefinition);
|
||||||
this.addAgentPolicy(mergedDefinition);
|
|
||||||
}
|
|
||||||
|
|
||||||
private addAgentPolicy(definition: AgentDefinition<z.ZodTypeAny>): void {
|
|
||||||
const policyEngine = this.config.getPolicyEngine();
|
|
||||||
if (!policyEngine) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the user has explicitly defined a policy for this tool, respect it.
|
|
||||||
// ignoreDynamic=true means we only check for rules NOT added by this registry.
|
|
||||||
if (policyEngine.hasRuleForTool(definition.name, true)) {
|
|
||||||
if (this.config.getDebugMode()) {
|
|
||||||
debugLogger.log(
|
|
||||||
`[AgentRegistry] User policy exists for '${definition.name}', skipping dynamic registration.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up any old dynamic policy for this tool (e.g. if we are overwriting an agent)
|
|
||||||
policyEngine.removeRulesForTool(definition.name, 'AgentRegistry (Dynamic)');
|
|
||||||
|
|
||||||
// Add the new dynamic policy
|
|
||||||
policyEngine.addRule({
|
|
||||||
toolName: definition.name,
|
|
||||||
decision:
|
|
||||||
definition.kind === 'local'
|
|
||||||
? PolicyDecision.ALLOW
|
|
||||||
: PolicyDecision.ASK_USER,
|
|
||||||
priority: PRIORITY_SUBAGENT_TOOL,
|
|
||||||
source: 'AgentRegistry (Dynamic)',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private isAgentEnabled<TOutput extends z.ZodTypeAny>(
|
private isAgentEnabled<TOutput extends z.ZodTypeAny>(
|
||||||
@@ -461,7 +427,6 @@ export class AgentRegistry {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.agents.set(definition.name, definition);
|
this.agents.set(definition.name, definition);
|
||||||
this.addAgentPolicy(definition);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const errorMessage = `Error loading A2A agent "${definition.name}": ${e instanceof Error ? e.message : String(e)}`;
|
const errorMessage = `Error loading A2A agent "${definition.name}": ${e instanceof Error ? e.message : String(e)}`;
|
||||||
debugLogger.warn(`[AgentRegistry] ${errorMessage}`, e);
|
debugLogger.warn(`[AgentRegistry] ${errorMessage}`, e);
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ export class SubagentTool extends BaseDeclarativeTool<AgentInputs, ToolResult> {
|
|||||||
messageBus,
|
messageBus,
|
||||||
/* isOutputMarkdown */ true,
|
/* isOutputMarkdown */ true,
|
||||||
/* canUpdateOutput */ true,
|
/* canUpdateOutput */ true,
|
||||||
|
/* toolAnnotations */ { isSubagent: true, agentKind: definition.kind },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Default policy for dynamically registered local subagents
|
||||||
|
[[rule]]
|
||||||
|
toolAnnotations = { isSubagent = true, agentKind = "local" }
|
||||||
|
decision = "allow"
|
||||||
|
priority = 50
|
||||||
|
|
||||||
|
# Default policy for dynamically registered remote subagents
|
||||||
|
[[rule]]
|
||||||
|
toolAnnotations = { isSubagent = true, agentKind = "remote" }
|
||||||
|
decision = "ask_user"
|
||||||
|
priority = 50
|
||||||
@@ -13,7 +13,6 @@ import {
|
|||||||
type SafetyCheckerRule,
|
type SafetyCheckerRule,
|
||||||
InProcessCheckerType,
|
InProcessCheckerType,
|
||||||
ApprovalMode,
|
ApprovalMode,
|
||||||
PRIORITY_SUBAGENT_TOOL,
|
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
import type { FunctionCall } from '@google/genai';
|
import type { FunctionCall } from '@google/genai';
|
||||||
import { SafetyCheckDecision } from '../safety/protocol.js';
|
import { SafetyCheckDecision } from '../safety/protocol.js';
|
||||||
@@ -1595,7 +1594,7 @@ describe('PolicyEngine', () => {
|
|||||||
{
|
{
|
||||||
toolName: 'unknown_subagent',
|
toolName: 'unknown_subagent',
|
||||||
decision: PolicyDecision.ALLOW,
|
decision: PolicyDecision.ALLOW,
|
||||||
priority: PRIORITY_SUBAGENT_TOOL,
|
priority: 1.05,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|||||||
import {
|
import {
|
||||||
PolicyDecision,
|
PolicyDecision,
|
||||||
ApprovalMode,
|
ApprovalMode,
|
||||||
PRIORITY_SUBAGENT_TOOL,
|
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
import * as fs from 'node:fs/promises';
|
import * as fs from 'node:fs/promises';
|
||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
@@ -911,12 +910,27 @@ priority = 100
|
|||||||
|
|
||||||
it('should override default subagent rules when in Plan Mode for unknown subagents', async () => {
|
it('should override default subagent rules when in Plan Mode for unknown subagents', async () => {
|
||||||
const planTomlPath = path.resolve(__dirname, 'policies', 'plan.toml');
|
const planTomlPath = path.resolve(__dirname, 'policies', 'plan.toml');
|
||||||
const fileContent = await fs.readFile(planTomlPath, 'utf-8');
|
const planFileContent = await fs.readFile(planTomlPath, 'utf-8');
|
||||||
|
|
||||||
|
const subagentsTomlPath = path.resolve(
|
||||||
|
__dirname,
|
||||||
|
'policies',
|
||||||
|
'subagents.toml',
|
||||||
|
);
|
||||||
|
const subagentsFileContent = await fs.readFile(subagentsTomlPath, 'utf-8');
|
||||||
|
|
||||||
const tempPolicyDir = await fs.mkdtemp(
|
const tempPolicyDir = await fs.mkdtemp(
|
||||||
path.join(os.tmpdir(), 'plan-policy-test-'),
|
path.join(os.tmpdir(), 'plan-policy-test-'),
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
await fs.writeFile(path.join(tempPolicyDir, 'plan.toml'), fileContent);
|
await fs.writeFile(
|
||||||
|
path.join(tempPolicyDir, 'plan.toml'),
|
||||||
|
planFileContent,
|
||||||
|
);
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(tempPolicyDir, 'subagents.toml'),
|
||||||
|
subagentsFileContent,
|
||||||
|
);
|
||||||
const getPolicyTier = () => 1; // Default tier
|
const getPolicyTier = () => 1; // Default tier
|
||||||
|
|
||||||
// 1. Load the actual Plan Mode policies
|
// 1. Load the actual Plan Mode policies
|
||||||
@@ -932,20 +946,14 @@ priority = 100
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 3. Simulate an unknown Subagent being registered (Dynamic Rule)
|
// 3. Simulate an unknown Subagent being registered (Dynamic Rule)
|
||||||
engine.addRule({
|
// Now handled by subagents.toml policy using toolAnnotations
|
||||||
toolName: 'unknown_subagent',
|
const checkResult = await engine.check(
|
||||||
decision: PolicyDecision.ALLOW,
|
{ name: 'unknown_subagent' },
|
||||||
priority: PRIORITY_SUBAGENT_TOOL,
|
{ isSubagent: true, agentKind: 'local' },
|
||||||
source: 'AgentRegistry (Dynamic)',
|
);
|
||||||
});
|
|
||||||
|
|
||||||
// 4. Verify Behavior:
|
// 4. Verify Behavior:
|
||||||
// The Plan Mode "Catch-All Deny" (from plan.toml) should override the Subagent Allow
|
// The Plan Mode "Catch-All Deny" (from plan.toml) should override the Subagent Allow
|
||||||
const checkResult = await engine.check(
|
|
||||||
{ name: 'unknown_subagent' },
|
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
checkResult.decision,
|
checkResult.decision,
|
||||||
'Unknown subagent should be DENIED in Plan Mode',
|
'Unknown subagent should be DENIED in Plan Mode',
|
||||||
|
|||||||
@@ -301,8 +301,3 @@ export interface CheckResult {
|
|||||||
rule?: PolicyRule;
|
rule?: PolicyRule;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Priority for subagent tools (registered dynamically).
|
|
||||||
* Effective priority matching Tier 1 (Default) read-only tools.
|
|
||||||
*/
|
|
||||||
export const PRIORITY_SUBAGENT_TOOL = 1.05;
|
|
||||||
|
|||||||
Reference in New Issue
Block a user