mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 22:21:22 -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.
|
out-of-the-box experience.
|
||||||
|
|
||||||
- **Read-only tools** (like `read_file`, `glob`) are generally **allowed**.
|
- **Read-only tools** (like `read_file`, `glob`) are generally **allowed**.
|
||||||
- **Agent delegation** (like `delegate_to_agent`) is **allowed** (sub-agent
|
- **Agent delegation** (like `delegate_to_agent`) defaults to **`ask_user`** to
|
||||||
actions are checked individually).
|
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
|
- **Write tools** (like `write_file`, `run_shell_command`) default to
|
||||||
**`ask_user`**.
|
**`ask_user`**.
|
||||||
- In **`yolo`** mode, a high-priority rule allows all tools.
|
- 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({
|
const invocation = tool.build({
|
||||||
agent_name: 'test_agent',
|
agent_name: 'test_agent',
|
||||||
arg1: 'valid',
|
arg1: 'valid',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Trigger confirmation check
|
// Trigger confirmation check
|
||||||
const p = invocation.shouldConfirmExecute(new AbortController().signal);
|
const result = await invocation.shouldConfirmExecute(
|
||||||
void p;
|
new AbortController().signal,
|
||||||
|
|
||||||
expect(messageBus.publish).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
type: MessageBusType.TOOL_CONFIRMATION_REQUEST,
|
|
||||||
toolCall: expect.objectContaining({
|
|
||||||
name: DELEGATE_TO_AGENT_TOOL_NAME,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
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 () => {
|
it('should delegate to remote agent correctly', async () => {
|
||||||
@@ -227,24 +231,27 @@ describe('DelegateToAgentTool', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('Confirmation', () => {
|
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({
|
const invocation = tool.build({
|
||||||
agent_name: 'test_agent',
|
agent_name: 'test_agent',
|
||||||
arg1: 'valid',
|
arg1: 'valid',
|
||||||
});
|
});
|
||||||
|
|
||||||
// We expect it to call messageBus.publish with 'delegate_to_agent'
|
// Local agents should now return false directly, bypassing policy check
|
||||||
// because super.shouldConfirmExecute checks the policy for the tool itself.
|
const result = await invocation.shouldConfirmExecute(
|
||||||
await invocation.shouldConfirmExecute(new AbortController().signal);
|
new AbortController().signal,
|
||||||
|
|
||||||
expect(messageBus.publish).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
type: MessageBusType.TOOL_CONFIRMATION_REQUEST,
|
|
||||||
toolCall: expect.objectContaining({
|
|
||||||
name: DELEGATE_TO_AGENT_TOOL_NAME,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
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 () => {
|
it('should forward to remote agent confirmation logic', async () => {
|
||||||
|
|||||||
@@ -172,7 +172,8 @@ class DelegateInvocation extends BaseToolInvocation<
|
|||||||
): Promise<ToolCallConfirmationDetails | false> {
|
): Promise<ToolCallConfirmationDetails | false> {
|
||||||
const definition = this.registry.getDefinition(this.params.agent_name);
|
const definition = this.registry.getDefinition(this.params.agent_name);
|
||||||
if (!definition || definition.kind !== 'remote') {
|
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;
|
const { agent_name: _agent_name, ...agentArgs } = this.params;
|
||||||
|
|||||||
@@ -27,5 +27,5 @@
|
|||||||
|
|
||||||
[[rule]]
|
[[rule]]
|
||||||
toolName = "delegate_to_agent"
|
toolName = "delegate_to_agent"
|
||||||
decision = "allow"
|
decision = "ask_user"
|
||||||
priority = 50
|
priority = 50
|
||||||
|
|||||||
Reference in New Issue
Block a user