mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 14:10:37 -07:00
fix(core): ensure silent local subagent delegation while allowing remote confirmation (#16395)
Co-authored-by: N. Taylor Mullen <ntaylormullen@google.com>
This commit is contained in:
@@ -258,8 +258,9 @@ The Gemini CLI ships with a set of default policies to provide a safe
|
||||
out-of-the-box experience.
|
||||
|
||||
- **Read-only tools** (like `read_file`, `glob`) are generally **allowed**.
|
||||
- **Agent delegation** (like `delegate_to_agent`) is **allowed** (sub-agent
|
||||
actions are checked individually).
|
||||
- **Agent delegation** (like `delegate_to_agent`) defaults to **`ask_user`** to
|
||||
ensure remote agents can prompt for confirmation, but local sub-agent actions
|
||||
are executed silently and checked individually.
|
||||
- **Write tools** (like `write_file`, `run_shell_command`) default to
|
||||
**`ask_user`**.
|
||||
- In **`yolo`** mode, a high-priority rule allows all tools.
|
||||
|
||||
@@ -187,24 +187,28 @@ describe('DelegateToAgentTool', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should use correct tool name "delegate_to_agent" when requesting confirmation', async () => {
|
||||
it('should execute local agents silently without requesting confirmation', async () => {
|
||||
const invocation = tool.build({
|
||||
agent_name: 'test_agent',
|
||||
arg1: 'valid',
|
||||
});
|
||||
|
||||
// Trigger confirmation check
|
||||
const p = invocation.shouldConfirmExecute(new AbortController().signal);
|
||||
void p;
|
||||
|
||||
expect(messageBus.publish).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: MessageBusType.TOOL_CONFIRMATION_REQUEST,
|
||||
toolCall: expect.objectContaining({
|
||||
name: DELEGATE_TO_AGENT_TOOL_NAME,
|
||||
}),
|
||||
}),
|
||||
const result = await invocation.shouldConfirmExecute(
|
||||
new AbortController().signal,
|
||||
);
|
||||
|
||||
expect(result).toBe(false);
|
||||
|
||||
// Verify it did NOT call messageBus.publish with 'delegate_to_agent'
|
||||
const delegateToAgentPublish = vi
|
||||
.mocked(messageBus.publish)
|
||||
.mock.calls.find(
|
||||
(call) =>
|
||||
call[0].type === MessageBusType.TOOL_CONFIRMATION_REQUEST &&
|
||||
call[0].toolCall.name === DELEGATE_TO_AGENT_TOOL_NAME,
|
||||
);
|
||||
expect(delegateToAgentPublish).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should delegate to remote agent correctly', async () => {
|
||||
@@ -227,24 +231,27 @@ describe('DelegateToAgentTool', () => {
|
||||
});
|
||||
|
||||
describe('Confirmation', () => {
|
||||
it('should use default behavior for local agents (super call)', async () => {
|
||||
it('should return false for local agents (silent execution)', async () => {
|
||||
const invocation = tool.build({
|
||||
agent_name: 'test_agent',
|
||||
arg1: 'valid',
|
||||
});
|
||||
|
||||
// We expect it to call messageBus.publish with 'delegate_to_agent'
|
||||
// because super.shouldConfirmExecute checks the policy for the tool itself.
|
||||
await invocation.shouldConfirmExecute(new AbortController().signal);
|
||||
|
||||
expect(messageBus.publish).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: MessageBusType.TOOL_CONFIRMATION_REQUEST,
|
||||
toolCall: expect.objectContaining({
|
||||
name: DELEGATE_TO_AGENT_TOOL_NAME,
|
||||
}),
|
||||
}),
|
||||
// Local agents should now return false directly, bypassing policy check
|
||||
const result = await invocation.shouldConfirmExecute(
|
||||
new AbortController().signal,
|
||||
);
|
||||
|
||||
expect(result).toBe(false);
|
||||
|
||||
const delegateToAgentPublish = vi
|
||||
.mocked(messageBus.publish)
|
||||
.mock.calls.find(
|
||||
(call) =>
|
||||
call[0].type === MessageBusType.TOOL_CONFIRMATION_REQUEST &&
|
||||
call[0].toolCall.name === DELEGATE_TO_AGENT_TOOL_NAME,
|
||||
);
|
||||
expect(delegateToAgentPublish).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should forward to remote agent confirmation logic', async () => {
|
||||
|
||||
@@ -172,7 +172,8 @@ class DelegateInvocation extends BaseToolInvocation<
|
||||
): Promise<ToolCallConfirmationDetails | false> {
|
||||
const definition = this.registry.getDefinition(this.params.agent_name);
|
||||
if (!definition || definition.kind !== 'remote') {
|
||||
return super.shouldConfirmExecute(abortSignal);
|
||||
// Local agents should execute without confirmation. Inner tool calls will bubble up their own confirmations to the user.
|
||||
return false;
|
||||
}
|
||||
|
||||
const { agent_name: _agent_name, ...agentArgs } = this.params;
|
||||
|
||||
@@ -27,5 +27,5 @@
|
||||
|
||||
[[rule]]
|
||||
toolName = "delegate_to_agent"
|
||||
decision = "allow"
|
||||
decision = "ask_user"
|
||||
priority = 50
|
||||
|
||||
Reference in New Issue
Block a user