mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-04 23:27:00 -07:00
Merge remote-tracking branch 'origin/main' into fix/windows-preflight-resilience
This commit is contained in:
+134
-18
@@ -38,6 +38,34 @@ main agent calls the tool, it delegates the task to the subagent. Once the
|
||||
subagent completes its task, it reports back to the main agent with its
|
||||
findings.
|
||||
|
||||
## How to use subagents
|
||||
|
||||
You can use subagents through automatic delegation or by explicitly forcing them
|
||||
in your prompt.
|
||||
|
||||
### Automatic delegation
|
||||
|
||||
Gemini CLI's main agent is instructed to use specialized subagents when a task
|
||||
matches their expertise. For example, if you ask "How does the auth system
|
||||
work?", the main agent may decide to call the `codebase_investigator` subagent
|
||||
to perform the research.
|
||||
|
||||
### Forcing a subagent (@ syntax)
|
||||
|
||||
You can explicitly direct a task to a specific subagent by using the `@` symbol
|
||||
followed by the subagent's name at the beginning of your prompt. This is useful
|
||||
when you want to bypass the main agent's decision-making and go straight to a
|
||||
specialist.
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
@codebase_investigator Map out the relationship between the AgentRegistry and the LocalAgentExecutor.
|
||||
```
|
||||
|
||||
When you use the `@` syntax, the CLI injects a system note that nudges the
|
||||
primary model to use that specific subagent tool immediately.
|
||||
|
||||
## Built-in subagents
|
||||
|
||||
Gemini CLI comes with the following built-in subagents:
|
||||
@@ -49,15 +77,17 @@ Gemini CLI comes with the following built-in subagents:
|
||||
dependencies.
|
||||
- **When to use:** "How does the authentication system work?", "Map out the
|
||||
dependencies of the `AgentRegistry` class."
|
||||
- **Configuration:** Enabled by default. You can configure it in
|
||||
`settings.json`. Example (forcing a specific model):
|
||||
- **Configuration:** Enabled by default. You can override its settings in
|
||||
`settings.json` under `agents.overrides`. Example (forcing a specific model
|
||||
and increasing turns):
|
||||
```json
|
||||
{
|
||||
"experimental": {
|
||||
"codebaseInvestigatorSettings": {
|
||||
"enabled": true,
|
||||
"maxNumTurns": 20,
|
||||
"model": "gemini-2.5-pro"
|
||||
"agents": {
|
||||
"overrides": {
|
||||
"codebase_investigator": {
|
||||
"modelConfig": { "model": "gemini-3-flash-preview" },
|
||||
"runConfig": { "maxTurns": 50 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,7 +263,7 @@ kind: local
|
||||
tools:
|
||||
- read_file
|
||||
- grep_search
|
||||
model: gemini-2.5-pro
|
||||
model: gemini-3-flash-preview
|
||||
temperature: 0.2
|
||||
max_turns: 10
|
||||
---
|
||||
@@ -254,16 +284,102 @@ it yourself; just report it.
|
||||
|
||||
### Configuration schema
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| :------------- | :----- | :------- | :------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `name` | string | Yes | Unique identifier (slug) used as the tool name for the agent. Only lowercase letters, numbers, hyphens, and underscores. |
|
||||
| `description` | string | Yes | Short description of what the agent does. This is visible to the main agent to help it decide when to call this subagent. |
|
||||
| `kind` | string | No | `local` (default) or `remote`. |
|
||||
| `tools` | array | No | List of tool names this agent can use. If omitted, it may have access to a default set. |
|
||||
| `model` | string | No | Specific model to use (e.g., `gemini-2.5-pro`). Defaults to `inherit` (uses the main session model). |
|
||||
| `temperature` | number | No | Model temperature (0.0 - 2.0). |
|
||||
| `max_turns` | number | No | Maximum number of conversation turns allowed for this agent before it must return. Defaults to `15`. |
|
||||
| `timeout_mins` | number | No | Maximum execution time in minutes. Defaults to `5`. |
|
||||
| Field | Type | Required | Description |
|
||||
| :------------- | :----- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `name` | string | Yes | Unique identifier (slug) used as the tool name for the agent. Only lowercase letters, numbers, hyphens, and underscores. |
|
||||
| `description` | string | Yes | Short description of what the agent does. This is visible to the main agent to help it decide when to call this subagent. |
|
||||
| `kind` | string | No | `local` (default) or `remote`. |
|
||||
| `tools` | array | No | List of tool names this agent can use. Supports wildcards: `*` (all tools), `mcp_*` (all MCP tools), `mcp_server_*` (all tools from a server). **If omitted, it inherits all tools from the parent session.** |
|
||||
| `model` | string | No | Specific model to use (e.g., `gemini-3-preview`). Defaults to `inherit` (uses the main session model). |
|
||||
| `temperature` | number | No | Model temperature (0.0 - 2.0). Defaults to `1`. |
|
||||
| `max_turns` | number | No | Maximum number of conversation turns allowed for this agent before it must return. Defaults to `30`. |
|
||||
| `timeout_mins` | number | No | Maximum execution time in minutes. Defaults to `10`. |
|
||||
|
||||
### Tool wildcards
|
||||
|
||||
When defining `tools` for a subagent, you can use wildcards to quickly grant
|
||||
access to groups of tools:
|
||||
|
||||
- `*`: Grant access to all available built-in and discovered tools.
|
||||
- `mcp_*`: Grant access to all tools from all connected MCP servers.
|
||||
- `mcp_my-server_*`: Grant access to all tools from a specific MCP server named
|
||||
`my-server`.
|
||||
|
||||
### Isolation and recursion protection
|
||||
|
||||
Each subagent runs in its own isolated context loop. This means:
|
||||
|
||||
- **Independent history:** The subagent's conversation history does not bloat
|
||||
the main agent's context.
|
||||
- **Isolated tools:** The subagent only has access to the tools you explicitly
|
||||
grant it.
|
||||
- **Recursion protection:** To prevent infinite loops and excessive token usage,
|
||||
subagents **cannot** call other subagents. If a subagent is granted the `*`
|
||||
tool wildcard, it will still be unable to see or invoke other agents.
|
||||
|
||||
## Managing subagents
|
||||
|
||||
You can manage subagents interactively using the `/agents` command or
|
||||
persistently via `settings.json`.
|
||||
|
||||
### Interactive management (/agents)
|
||||
|
||||
If you are in an interactive CLI session, you can use the `/agents` command to
|
||||
manage subagents without editing configuration files manually. This is the
|
||||
recommended way to quickly enable, disable, or re-configure agents on the fly.
|
||||
|
||||
For a full list of sub-commands and usage, see the
|
||||
[`/agents` command reference](../reference/commands.md#agents).
|
||||
|
||||
### Persistent configuration (settings.json)
|
||||
|
||||
While the `/agents` command and agent definition files provide a starting point,
|
||||
you can use `settings.json` for global, persistent overrides. This is useful for
|
||||
enforcing specific models or execution limits across all sessions.
|
||||
|
||||
#### `agents.overrides`
|
||||
|
||||
Use this to enable or disable specific agents or override their run
|
||||
configurations.
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"overrides": {
|
||||
"security-auditor": {
|
||||
"enabled": false,
|
||||
"runConfig": {
|
||||
"maxTurns": 20,
|
||||
"maxTimeMinutes": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `modelConfigs.overrides`
|
||||
|
||||
You can target specific subagents with custom model settings (like system
|
||||
instruction prefixes or specific safety settings) using the `overrideScope`
|
||||
field.
|
||||
|
||||
```json
|
||||
{
|
||||
"modelConfigs": {
|
||||
"overrides": [
|
||||
{
|
||||
"match": { "overrideScope": "security-auditor" },
|
||||
"modelConfig": {
|
||||
"generateContentConfig": {
|
||||
"temperature": 0.1
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Optimizing your subagent
|
||||
|
||||
|
||||
@@ -14,6 +14,31 @@ Slash commands provide meta-level control over the CLI itself.
|
||||
|
||||
- **Description:** Show version info. Share this information when filing issues.
|
||||
|
||||
### `/agents`
|
||||
|
||||
- **Description:** Manage local and remote subagents.
|
||||
- **Note:** This command is experimental and requires
|
||||
`experimental.enableAgents: true` in your `settings.json`.
|
||||
- **Sub-commands:**
|
||||
- **`list`**:
|
||||
- **Description:** Lists all discovered agents, including built-in, local,
|
||||
and remote agents.
|
||||
- **Usage:** `/agents list`
|
||||
- **`reload`** (alias: `refresh`):
|
||||
- **Description:** Rescans agent directories (`~/.gemini/agents` and
|
||||
`.gemini/agents`) and reloads the registry.
|
||||
- **Usage:** `/agents reload`
|
||||
- **`enable`**:
|
||||
- **Description:** Enables a specific subagent.
|
||||
- **Usage:** `/agents enable <agent-name>`
|
||||
- **`disable`**:
|
||||
- **Description:** Disables a specific subagent.
|
||||
- **Usage:** `/agents disable <agent-name>`
|
||||
- **`config`**:
|
||||
- **Description:** Opens a configuration dialog for the specified agent to
|
||||
adjust its model, temperature, or execution limits.
|
||||
- **Usage:** `/agents config <agent-name>`
|
||||
|
||||
### `/auth`
|
||||
|
||||
- **Description:** Open a dialog that lets you change the authentication method.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"write_file","args":{"file_path":"test.txt","content":"hello"}}},{"text":"I've successfully written \"hello\" to test.txt. The file has been created with the specified content."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":50,"totalTokenCount":150}}]}
|
||||
@@ -203,4 +203,33 @@ describe.skipIf(!chromeAvailable)('browser-agent', () => {
|
||||
// Should successfully complete all operations
|
||||
assertModelHasOutput(result);
|
||||
});
|
||||
|
||||
it('should handle tool confirmation for write_file without crashing', async () => {
|
||||
rig.setup('tool-confirmation', {
|
||||
fakeResponsesPath: join(
|
||||
__dirname,
|
||||
'browser-agent.confirmation.responses',
|
||||
),
|
||||
settings: {
|
||||
agents: {
|
||||
browser_agent: {
|
||||
headless: true,
|
||||
sessionMode: 'isolated',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const run = await rig.runInteractive({ approvalMode: 'default' });
|
||||
|
||||
await run.type('Write hello to test.txt');
|
||||
await run.type('\r');
|
||||
|
||||
await run.expectText('Allow', 15000);
|
||||
|
||||
await run.type('y');
|
||||
await run.type('\r');
|
||||
|
||||
await run.expectText('successfully written', 15000);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -116,38 +116,9 @@ const Kbd = ({ name, shortcut }: { name: string; shortcut: string }) => (
|
||||
</>
|
||||
);
|
||||
|
||||
/**
|
||||
* Loading state component displayed while sessions are being loaded.
|
||||
*/
|
||||
const SessionBrowserLoading = (): React.JSX.Element => (
|
||||
<Box flexDirection="column" paddingX={1}>
|
||||
<Text color={Colors.Gray}>Loading sessions…</Text>
|
||||
</Box>
|
||||
);
|
||||
|
||||
/**
|
||||
* Error state component displayed when session loading fails.
|
||||
*/
|
||||
const SessionBrowserError = ({
|
||||
state,
|
||||
}: {
|
||||
state: SessionBrowserState;
|
||||
}): React.JSX.Element => (
|
||||
<Box flexDirection="column" paddingX={1}>
|
||||
<Text color={Colors.AccentRed}>Error: {state.error}</Text>
|
||||
<Text color={Colors.Gray}>Press q to exit</Text>
|
||||
</Box>
|
||||
);
|
||||
|
||||
/**
|
||||
* Empty state component displayed when no sessions are found.
|
||||
*/
|
||||
const SessionBrowserEmpty = (): React.JSX.Element => (
|
||||
<Box flexDirection="column" paddingX={1}>
|
||||
<Text color={Colors.Gray}>No auto-saved conversations found.</Text>
|
||||
<Text color={Colors.Gray}>Press q to exit</Text>
|
||||
</Box>
|
||||
);
|
||||
import { SessionBrowserLoading } from './SessionBrowser/SessionBrowserLoading.js';
|
||||
import { SessionBrowserError } from './SessionBrowser/SessionBrowserError.js';
|
||||
import { SessionBrowserEmpty } from './SessionBrowser/SessionBrowserEmpty.js';
|
||||
|
||||
import { sortSessions, filterSessions } from './SessionBrowser/utils.js';
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { Colors } from '../../colors.js';
|
||||
|
||||
/**
|
||||
* Empty state component displayed when no sessions are found.
|
||||
*/
|
||||
export const SessionBrowserEmpty = (): React.JSX.Element => (
|
||||
<Box flexDirection="column" paddingX={1}>
|
||||
<Text color={Colors.Gray}>No auto-saved conversations found.</Text>
|
||||
<Text color={Colors.Gray}>Press q to exit</Text>
|
||||
</Box>
|
||||
);
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { Colors } from '../../colors.js';
|
||||
import type { SessionBrowserState } from '../SessionBrowser.js';
|
||||
|
||||
/**
|
||||
* Error state component displayed when session loading fails.
|
||||
*/
|
||||
export const SessionBrowserError = ({
|
||||
state,
|
||||
}: {
|
||||
state: SessionBrowserState;
|
||||
}): React.JSX.Element => (
|
||||
<Box flexDirection="column" paddingX={1}>
|
||||
<Text color={Colors.AccentRed}>Error: {state.error}</Text>
|
||||
<Text color={Colors.Gray}>Press q to exit</Text>
|
||||
</Box>
|
||||
);
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { Colors } from '../../colors.js';
|
||||
|
||||
/**
|
||||
* Loading state component displayed while sessions are being loaded.
|
||||
*/
|
||||
export const SessionBrowserLoading = (): React.JSX.Element => (
|
||||
<Box flexDirection="column" paddingX={1}>
|
||||
<Text color={Colors.Gray}>Loading sessions…</Text>
|
||||
</Box>
|
||||
);
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { render } from '../../../test-utils/render.js';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { SessionBrowserLoading } from './SessionBrowserLoading.js';
|
||||
import { SessionBrowserError } from './SessionBrowserError.js';
|
||||
import { SessionBrowserEmpty } from './SessionBrowserEmpty.js';
|
||||
import type { SessionBrowserState } from '../SessionBrowser.js';
|
||||
|
||||
describe('SessionBrowser UI States', () => {
|
||||
it('SessionBrowserLoading renders correctly', async () => {
|
||||
const { lastFrame, waitUntilReady } = render(<SessionBrowserLoading />);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('SessionBrowserError renders correctly', async () => {
|
||||
const mockState = { error: 'Test error message' } as SessionBrowserState;
|
||||
const { lastFrame, waitUntilReady } = render(
|
||||
<SessionBrowserError state={mockState} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('SessionBrowserEmpty renders correctly', async () => {
|
||||
const { lastFrame, waitUntilReady } = render(<SessionBrowserEmpty />);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`SessionBrowser UI States > SessionBrowserEmpty renders correctly 1`] = `
|
||||
" No auto-saved conversations found.
|
||||
Press q to exit
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`SessionBrowser UI States > SessionBrowserError renders correctly 1`] = `
|
||||
" Error: Test error message
|
||||
Press q to exit
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`SessionBrowser UI States > SessionBrowserLoading renders correctly 1`] = `
|
||||
" Loading sessions…
|
||||
"
|
||||
`;
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
type ToolInvocation,
|
||||
type ToolResult,
|
||||
} from '../tools/tools.js';
|
||||
import type { Config } from '../config/config.js';
|
||||
|
||||
import { type AgentLoopContext } from '../config/agent-loop-context.js';
|
||||
import type { AgentDefinition, AgentInputs } from './types.js';
|
||||
import { LocalSubagentInvocation } from './local-invocation.js';
|
||||
@@ -54,10 +54,6 @@ export class SubagentToolWrapper extends BaseDeclarativeTool<
|
||||
);
|
||||
}
|
||||
|
||||
private get config(): Config {
|
||||
return this.context.config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an invocation instance for executing the subagent.
|
||||
*
|
||||
@@ -89,7 +85,7 @@ export class SubagentToolWrapper extends BaseDeclarativeTool<
|
||||
// Special handling for browser agent - needs async MCP setup
|
||||
if (definition.name === BROWSER_AGENT_NAME) {
|
||||
return new BrowserAgentInvocation(
|
||||
this.config,
|
||||
this.context,
|
||||
params,
|
||||
effectiveMessageBus,
|
||||
_toolName,
|
||||
|
||||
@@ -676,6 +676,43 @@ describe('policy.ts', () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should work when context is created via Object.create (prototype chain)', async () => {
|
||||
const mockConfig = {
|
||||
setApprovalMode: vi.fn(),
|
||||
} as unknown as Mocked<Config>;
|
||||
const mockMessageBus = {
|
||||
publish: vi.fn(),
|
||||
} as unknown as Mocked<MessageBus>;
|
||||
|
||||
const baseContext = {
|
||||
config: mockConfig,
|
||||
messageBus: mockMessageBus,
|
||||
};
|
||||
const protoContext: AgentLoopContext = Object.create(baseContext);
|
||||
|
||||
expect(Object.keys(protoContext)).toHaveLength(0);
|
||||
expect(protoContext.config).toBe(mockConfig);
|
||||
expect(protoContext.messageBus).toBe(mockMessageBus);
|
||||
|
||||
const tool = { name: 'test-tool' } as AnyDeclarativeTool;
|
||||
|
||||
await updatePolicy(
|
||||
tool,
|
||||
ToolConfirmationOutcome.ProceedAlways,
|
||||
undefined,
|
||||
protoContext,
|
||||
mockMessageBus,
|
||||
);
|
||||
|
||||
expect(mockMessageBus.publish).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: MessageBusType.UPDATE_POLICY,
|
||||
toolName: 'test-tool',
|
||||
persist: false,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPolicyDenialError', () => {
|
||||
|
||||
Reference in New Issue
Block a user