mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-24 00:30:59 -07:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 98b3d6f47d | |||
| bb85e970f3 | |||
| 8615315711 | |||
| c9a336976b | |||
| 06a7873c51 | |||
| 0e66f545ca | |||
| 98d1bec99f | |||
| 08063d7b0a | |||
| 2ebcd48a4e | |||
| 36dbaa8462 | |||
| 4fc059beb5 | |||
| 46ec71bf0e | |||
| 33f630111f | |||
| b3ebab308e | |||
| c02effc0cf | |||
| ec43305d2d | |||
| 4983053ae3 |
@@ -2,7 +2,8 @@
|
||||
"experimental": {
|
||||
"plan": true,
|
||||
"extensionReloading": true,
|
||||
"modelSteering": true
|
||||
"modelSteering": true,
|
||||
"memoryManager": true
|
||||
},
|
||||
"general": {
|
||||
"devtools": true
|
||||
|
||||
@@ -71,12 +71,44 @@ accessible.
|
||||
tables).
|
||||
- **Media:** Use lowercase hyphenated filenames. Provide descriptive alt text
|
||||
for all images.
|
||||
- **Details section:** Use the `<details>` tag to create a collapsible section.
|
||||
This is useful for supplementary or data-heavy information that isn't critical
|
||||
to the main flow.
|
||||
|
||||
Example:
|
||||
|
||||
<details>
|
||||
<summary>Title</summary>
|
||||
|
||||
- First entry
|
||||
- Second entry
|
||||
|
||||
</details>
|
||||
|
||||
- **Callouts**: Use GitHub-flavored markdown alerts to highlight important
|
||||
information. To ensure the formatting is preserved by `npm run format`, place
|
||||
an empty line, then the `<!-- prettier-ignore -->` comment directly before
|
||||
the callout block. The callout type (`[!TYPE]`) should be on the first line,
|
||||
followed by a newline, and then the content, with each subsequent line of
|
||||
content starting with `>`. Available types are `NOTE`, `TIP`, `IMPORTANT`,
|
||||
`WARNING`, and `CAUTION`.
|
||||
|
||||
Example:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> This is an example of a multi-line note that will be preserved
|
||||
> by Prettier.
|
||||
|
||||
### Structure
|
||||
- **BLUF:** Start with an introduction explaining what to expect.
|
||||
- **Experimental features:** If a feature is clearly noted as experimental,
|
||||
add the following note immediately after the introductory paragraph:
|
||||
`> **Note:** This is a preview feature currently under active development.`
|
||||
add the following note immediately after the introductory paragraph:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> This is an experimental feature currently under active development.
|
||||
|
||||
- **Headings:** Use hierarchical headings to support the user journey.
|
||||
- **Procedures:**
|
||||
- Introduce lists of steps with a complete sentence.
|
||||
@@ -85,8 +117,7 @@ add the following note immediately after the introductory paragraph:
|
||||
- Put conditions before instructions (e.g., "On the Settings page, click...").
|
||||
- Provide clear context for where the action takes place.
|
||||
- Indicate optional steps clearly (e.g., "Optional: ...").
|
||||
- **Elements:** Use bullet lists, tables, notes (`> **Note:**`), and warnings
|
||||
(`> **Warning:**`).
|
||||
- **Elements:** Use bullet lists, tables, details, and callouts.
|
||||
- **Avoid using a table of contents:** If a table of contents is present, remove
|
||||
it.
|
||||
- **Next steps:** Conclude with a "Next steps" section if applicable.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
packages/core/src/services/scripts/*.exe
|
||||
@@ -1,7 +1,9 @@
|
||||
name: 'Website issue'
|
||||
description: 'Report an issue with the Gemini CLI Website and Gemini CLI Extensions Gallery'
|
||||
title: 'GeminiCLI.com Feedback: [ISSUE]'
|
||||
labels:
|
||||
- 'area/extensions'
|
||||
- 'area/documentation'
|
||||
body:
|
||||
- type: 'markdown'
|
||||
attributes:
|
||||
|
||||
@@ -106,6 +106,67 @@ organization.
|
||||
ensures users maintain final control over which permitted servers are actually
|
||||
active in their environment.
|
||||
|
||||
#### Required MCP Servers (preview)
|
||||
|
||||
**Default**: empty
|
||||
|
||||
Allows administrators to define MCP servers that are **always injected** into
|
||||
the user's environment. Unlike the allowlist (which filters user-configured
|
||||
servers), required servers are automatically added regardless of the user's
|
||||
local configuration.
|
||||
|
||||
**Required Servers Format:**
|
||||
|
||||
```json
|
||||
{
|
||||
"requiredMcpServers": {
|
||||
"corp-compliance-tool": {
|
||||
"url": "https://mcp.corp/compliance",
|
||||
"type": "http",
|
||||
"trust": true,
|
||||
"description": "Corporate compliance tool"
|
||||
},
|
||||
"internal-registry": {
|
||||
"url": "https://registry.corp/mcp",
|
||||
"type": "sse",
|
||||
"authProviderType": "google_credentials",
|
||||
"oauth": {
|
||||
"scopes": ["https://www.googleapis.com/auth/scope"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Supported Fields:**
|
||||
|
||||
- `url`: (Required) The full URL of the MCP server endpoint.
|
||||
- `type`: (Required) The connection type (`sse` or `http`).
|
||||
- `trust`: (Optional) If set to `true`, tool execution will not require user
|
||||
approval. Defaults to `true` for required servers.
|
||||
- `description`: (Optional) Human-readable description of the server.
|
||||
- `authProviderType`: (Optional) Authentication provider (`dynamic_discovery`,
|
||||
`google_credentials`, or `service_account_impersonation`).
|
||||
- `oauth`: (Optional) OAuth configuration including `scopes`, `clientId`, and
|
||||
`clientSecret`.
|
||||
- `targetAudience`: (Optional) OAuth target audience for service-to-service
|
||||
auth.
|
||||
- `targetServiceAccount`: (Optional) Service account email to impersonate.
|
||||
- `headers`: (Optional) Additional HTTP headers to send with requests.
|
||||
- `includeTools` / `excludeTools`: (Optional) Tool filtering lists.
|
||||
- `timeout`: (Optional) Timeout in milliseconds for MCP requests.
|
||||
|
||||
**Client Enforcement Logic:**
|
||||
|
||||
- Required servers are injected **after** allowlist filtering, so they are
|
||||
always available even if the allowlist is active.
|
||||
- If a required server has the **same name** as a locally configured server, the
|
||||
admin configuration **completely overrides** the local one.
|
||||
- Required servers only support remote transports (`sse`, `http`). Local
|
||||
execution fields (`command`, `args`, `env`, `cwd`) are not supported.
|
||||
- Required servers can coexist with allowlisted servers — both features work
|
||||
independently.
|
||||
|
||||
### Unmanaged Capabilities
|
||||
|
||||
**Enabled/Disabled** | Default: disabled
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Preview release: v0.35.0-preview.1
|
||||
# Preview release: v0.35.0-preview.2
|
||||
|
||||
Released: March 17, 2026
|
||||
Released: March 19, 2026
|
||||
|
||||
Our preview release includes the latest, new, and experimental features. This
|
||||
release may not be as stable as our [latest weekly release](latest.md).
|
||||
@@ -33,6 +33,10 @@ npm install -g @google/gemini-cli@preview
|
||||
|
||||
## What's Changed
|
||||
|
||||
- fix(patch): cherry-pick 4e5dfd0 to release/v0.35.0-preview.1-pr-23074 to patch
|
||||
version v0.35.0-preview.1 and create version 0.35.0-preview.2 by
|
||||
@gemini-cli-robot in
|
||||
[#23134](https://github.com/google-gemini/gemini-cli/pull/23134)
|
||||
- feat(cli): customizable keyboard shortcuts by @scidomino in
|
||||
[#21945](https://github.com/google-gemini/gemini-cli/pull/21945)
|
||||
- feat(core): Thread `AgentLoopContext` through core. by @joshualitt in
|
||||
@@ -373,4 +377,4 @@ npm install -g @google/gemini-cli@preview
|
||||
[#22815](https://github.com/google-gemini/gemini-cli/pull/22815)
|
||||
|
||||
**Full Changelog**:
|
||||
https://github.com/google-gemini/gemini-cli/compare/v0.34.0-preview.4...v0.35.0-preview.1
|
||||
https://github.com/google-gemini/gemini-cli/compare/v0.34.0-preview.4...v0.35.0-preview.2
|
||||
|
||||
@@ -39,7 +39,9 @@ file in your project's temporary directory, typically located at
|
||||
The Checkpointing feature is disabled by default. To enable it, you need to edit
|
||||
your `settings.json` file.
|
||||
|
||||
> **Note:** The `--checkpointing` command-line flag was removed in version
|
||||
<!-- prettier-ignore -->
|
||||
> [!CAUTION]
|
||||
> The `--checkpointing` command-line flag was removed in version
|
||||
> 0.11.0. Checkpointing can now only be enabled through the `settings.json`
|
||||
> configuration file.
|
||||
|
||||
|
||||
@@ -30,7 +30,9 @@ separator (`/` or `\`) being converted to a colon (`:`).
|
||||
- A file at `<project>/.gemini/commands/git/commit.toml` becomes the namespaced
|
||||
command `/git:commit`.
|
||||
|
||||
> [!TIP] After creating or modifying `.toml` command files, run
|
||||
<!-- prettier-ignore -->
|
||||
> [!TIP]
|
||||
> After creating or modifying `.toml` command files, run
|
||||
> `/commands reload` to pick up your changes without restarting the CLI.
|
||||
|
||||
## TOML file format (v1)
|
||||
@@ -177,10 +179,10 @@ ensure that only intended commands can be run.
|
||||
automatically shell-escaped (see
|
||||
[Context-Aware Injection](#1-context-aware-injection-with-args) above).
|
||||
3. **Robust parsing:** The parser correctly handles complex shell commands that
|
||||
include nested braces, such as JSON payloads. **Note:** The content inside
|
||||
`!{...}` must have balanced braces (`{` and `}`). If you need to execute a
|
||||
command containing unbalanced braces, consider wrapping it in an external
|
||||
script file and calling the script within the `!{...}` block.
|
||||
include nested braces, such as JSON payloads. The content inside `!{...}`
|
||||
must have balanced braces (`{` and `}`). If you need to execute a command
|
||||
containing unbalanced braces, consider wrapping it in an external script
|
||||
file and calling the script within the `!{...}` block.
|
||||
4. **Security check and confirmation:** The CLI performs a security check on
|
||||
the final, resolved command (after arguments are escaped and substituted). A
|
||||
dialog will appear showing the exact command(s) to be executed.
|
||||
|
||||
+15
-9
@@ -5,9 +5,11 @@ and managing Gemini CLI in an enterprise environment. By leveraging system-level
|
||||
settings, administrators can enforce security policies, manage tool access, and
|
||||
ensure a consistent experience for all users.
|
||||
|
||||
> **A note on security:** The patterns described in this document are intended
|
||||
> to help administrators create a more controlled and secure environment for
|
||||
> using Gemini CLI. However, they should not be considered a foolproof security
|
||||
<!-- prettier-ignore -->
|
||||
> [!WARNING]
|
||||
> The patterns described in this document are intended to help
|
||||
> administrators create a more controlled and secure environment for using
|
||||
> Gemini CLI. However, they should not be considered a foolproof security
|
||||
> boundary. A determined user with sufficient privileges on their local machine
|
||||
> may still be able to circumvent these configurations. These measures are
|
||||
> designed to prevent accidental misuse and enforce corporate policy in a
|
||||
@@ -280,10 +282,12 @@ environment to a blocklist.
|
||||
}
|
||||
```
|
||||
|
||||
**Security note:** Blocklisting with `excludeTools` is less secure than
|
||||
allowlisting with `coreTools`, as it relies on blocking known-bad commands, and
|
||||
clever users may find ways to bypass simple string-based blocks. **Allowlisting
|
||||
is the recommended approach.**
|
||||
<!-- prettier-ignore -->
|
||||
> [!WARNING]
|
||||
> Blocklisting with `excludeTools` is less secure than
|
||||
> allowlisting with `coreTools`, as it relies on blocking known-bad commands,
|
||||
> and clever users may find ways to bypass simple string-based blocks.
|
||||
> **Allowlisting is the recommended approach.**
|
||||
|
||||
### Disabling YOLO mode
|
||||
|
||||
@@ -494,8 +498,10 @@ other events. For more information, see the
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** Ensure that `logPrompts` is set to `false` in an enterprise setting to
|
||||
avoid collecting potentially sensitive information from user prompts.
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> Ensure that `logPrompts` is set to `false` in an enterprise setting to
|
||||
> avoid collecting potentially sensitive information from user prompts.
|
||||
|
||||
## Authentication
|
||||
|
||||
|
||||
@@ -4,9 +4,10 @@ Model steering lets you provide real-time guidance and feedback to Gemini CLI
|
||||
while it is actively executing a task. This lets you correct course, add missing
|
||||
context, or skip unnecessary steps without having to stop and restart the agent.
|
||||
|
||||
> **Note:** This is a preview feature under active development. Preview features
|
||||
> may only be available in the **Preview** channel or may need to be enabled
|
||||
> under `/settings`.
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> This is an experimental feature currently under active development and
|
||||
> may need to be enabled under `/settings`.
|
||||
|
||||
Model steering is particularly useful during complex [Plan Mode](./plan-mode.md)
|
||||
workflows or long-running subagent executions where you want to ensure the agent
|
||||
|
||||
+3
-1
@@ -5,7 +5,9 @@ used by Gemini CLI, giving you more control over your results. Use **Pro**
|
||||
models for complex tasks and reasoning, **Flash** models for high speed results,
|
||||
or the (recommended) **Auto** setting to choose the best model for your tasks.
|
||||
|
||||
> **Note:** The `/model` command (and the `--model` flag) does not override the
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> The `/model` command (and the `--model` flag) does not override the
|
||||
> model used by sub-agents. Consequently, even when using the `/model` flag you
|
||||
> may see other models used in your model usage reports.
|
||||
|
||||
|
||||
@@ -4,9 +4,10 @@ Gemini CLI can send system notifications to alert you when a session completes
|
||||
or when it needs your attention, such as when it's waiting for you to approve a
|
||||
tool call.
|
||||
|
||||
> **Note:** This is a preview feature currently under active development.
|
||||
> Preview features may be available on the **Preview** channel or may need to be
|
||||
> enabled under `/settings`.
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> This is an experimental feature currently under active development and
|
||||
> may need to be enabled under `/settings`.
|
||||
|
||||
Notifications are particularly useful when running long-running tasks or using
|
||||
[Plan Mode](./plan-mode.md), letting you switch to other windows while Gemini
|
||||
|
||||
@@ -35,19 +35,17 @@ To launch Gemini CLI in Plan Mode once:
|
||||
To start Plan Mode while using Gemini CLI:
|
||||
|
||||
- **Keyboard shortcut:** Press `Shift+Tab` to cycle through approval modes
|
||||
(`Default` -> `Auto-Edit` -> `Plan`).
|
||||
|
||||
> **Note:** Plan Mode is automatically removed from the rotation when Gemini
|
||||
> CLI is actively processing or showing confirmation dialogs.
|
||||
(`Default` -> `Auto-Edit` -> `Plan`). Plan Mode is automatically removed from
|
||||
the rotation when Gemini CLI is actively processing or showing confirmation
|
||||
dialogs.
|
||||
|
||||
- **Command:** Type `/plan` in the input box.
|
||||
|
||||
- **Natural Language:** Ask Gemini CLI to "start a plan for...". Gemini CLI
|
||||
calls the
|
||||
[`enter_plan_mode`](../tools/planning.md#1-enter_plan_mode-enterplanmode) tool
|
||||
to switch modes.
|
||||
> **Note:** This tool is not available when Gemini CLI is in
|
||||
> [YOLO mode](../reference/configuration.md#command-line-arguments).
|
||||
to switch modes. This tool is not available when Gemini CLI is in
|
||||
[YOLO mode](../reference/configuration.md#command-line-arguments).
|
||||
|
||||
## How to use Plan Mode
|
||||
|
||||
@@ -407,7 +405,9 @@ To build a custom planning workflow, you can use:
|
||||
[custom plan directories](#custom-plan-directory-and-policies) and
|
||||
[custom policies](#custom-policies).
|
||||
|
||||
> **Note:** Use [Conductor] as a reference when building your own custom
|
||||
<!-- prettier-ignore -->
|
||||
> [!TIP]
|
||||
> Use [Conductor] as a reference when building your own custom
|
||||
> planning workflow.
|
||||
|
||||
By using Plan Mode as its execution environment, your custom methodology can
|
||||
|
||||
+24
-4
@@ -50,7 +50,25 @@ Cross-platform sandboxing with complete process isolation.
|
||||
**Note**: Requires building the sandbox image locally or using a published image
|
||||
from your organization's registry.
|
||||
|
||||
### 3. gVisor / runsc (Linux only)
|
||||
### 3. Windows Native Sandbox (Windows only)
|
||||
|
||||
... **Troubleshooting and Side Effects:**
|
||||
|
||||
The Windows Native sandbox uses the `icacls` command to set a "Low Mandatory
|
||||
Level" on files and directories it needs to write to.
|
||||
|
||||
- **Persistence**: These integrity level changes are persistent on the
|
||||
filesystem. Even after the sandbox session ends, files created or modified by
|
||||
the sandbox will retain their "Low" integrity level.
|
||||
- **Manual Reset**: If you need to reset the integrity level of a file or
|
||||
directory, you can use:
|
||||
```powershell
|
||||
icacls "C:\path\to\dir" /setintegritylevel Medium
|
||||
```
|
||||
- **System Folders**: The sandbox manager automatically skips setting integrity
|
||||
levels on system folders (like `C:\Windows`) for safety.
|
||||
|
||||
### 4. gVisor / runsc (Linux only)
|
||||
|
||||
Strongest isolation available: runs containers inside a user-space kernel via
|
||||
[gVisor](https://github.com/google/gvisor). gVisor intercepts all container
|
||||
@@ -253,9 +271,11 @@ $env:SANDBOX_SET_UID_GID="false" # Disable UID/GID mapping
|
||||
DEBUG=1 gemini -s -p "debug command"
|
||||
```
|
||||
|
||||
**Note:** If you have `DEBUG=true` in a project's `.env` file, it won't affect
|
||||
gemini-cli due to automatic exclusion. Use `.gemini/.env` files for gemini-cli
|
||||
specific debug settings.
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> If you have `DEBUG=true` in a project's `.env` file, it won't affect
|
||||
> gemini-cli due to automatic exclusion. Use `.gemini/.env` files for
|
||||
> gemini-cli specific debug settings.
|
||||
|
||||
### Inspect sandbox
|
||||
|
||||
|
||||
@@ -11,7 +11,9 @@ locations:
|
||||
- **User settings**: `~/.gemini/settings.json`
|
||||
- **Workspace settings**: `your-project/.gemini/settings.json`
|
||||
|
||||
Note: Workspace settings override user settings.
|
||||
<!-- prettier-ignore -->
|
||||
> [!IMPORTANT]
|
||||
> Workspace settings override user settings.
|
||||
|
||||
## Settings reference
|
||||
|
||||
@@ -115,6 +117,8 @@ they appear in the UI.
|
||||
|
||||
| UI Label | Setting | Description | Default |
|
||||
| -------------------------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
|
||||
| Sandbox Allowed Paths | `tools.sandboxAllowedPaths` | List of additional paths that the sandbox is allowed to access. | `[]` |
|
||||
| Sandbox Network Access | `tools.sandboxNetworkAccess` | Whether the sandbox is allowed to access the network. | `false` |
|
||||
| Enable Interactive Shell | `tools.shell.enableInteractiveShell` | Use node-pty for an interactive shell experience. Fallback to child_process still applies. | `true` |
|
||||
| Show Color | `tools.shell.showColor` | Show color in shell output. | `false` |
|
||||
| Use Ripgrep | `tools.useRipgrep` | Use ripgrep for file content search instead of the fallback implementation. Provides faster search performance. | `true` |
|
||||
@@ -152,6 +156,7 @@ they appear in the UI.
|
||||
| Plan | `experimental.plan` | Enable Plan Mode. | `true` |
|
||||
| Model Steering | `experimental.modelSteering` | Enable model steering (user hints) to guide the model during tool execution. | `false` |
|
||||
| Direct Web Fetch | `experimental.directWebFetch` | Enable web fetch behavior that bypasses LLM summarization. | `false` |
|
||||
| Memory Manager Agent | `experimental.memoryManager` | Replace the built-in save_memory tool with a memory manager subagent that supports adding, removing, de-duplicating, and organizing memories. | `false` |
|
||||
| Topic & Update Narration | `experimental.topicUpdateNarration` | Enable the experimental Topic & Update communication model for reduced chattiness and structured progress reporting. | `false` |
|
||||
|
||||
### Skills
|
||||
|
||||
+4
-2
@@ -63,8 +63,10 @@ Use the `/skills` slash command to view and manage available expertise:
|
||||
- `/skills enable <name>`: Re-enables a disabled skill.
|
||||
- `/skills reload`: Refreshes the list of discovered skills from all tiers.
|
||||
|
||||
_Note: `/skills disable` and `/skills enable` default to the `user` scope. Use
|
||||
`--scope workspace` to manage workspace-specific settings._
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> `/skills disable` and `/skills enable` default to the `user` scope. Use
|
||||
> `--scope workspace` to manage workspace-specific settings.
|
||||
|
||||
### From the Terminal
|
||||
|
||||
|
||||
@@ -14,7 +14,9 @@ core instructions will apply unless you include them yourself.
|
||||
This feature is intended for advanced users who need to enforce strict,
|
||||
project-specific behavior or create a customized persona.
|
||||
|
||||
> Tip: You can export the current default system prompt to a file first, review
|
||||
<!-- prettier-ignore -->
|
||||
> [!TIP]
|
||||
> You can export the current default system prompt to a file first, review
|
||||
> it, and then selectively modify or replace it (see
|
||||
> [“Export the default prompt”](#export-the-default-prompt-recommended)).
|
||||
|
||||
|
||||
@@ -125,9 +125,11 @@ You must complete several setup steps before enabling Google Cloud telemetry.
|
||||
}
|
||||
```
|
||||
|
||||
> **Note:** This setting requires **Direct export** (in-process exporters)
|
||||
> and cannot be used when `useCollector` is `true`. If both are enabled,
|
||||
> telemetry will be disabled.
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> This setting requires **Direct export** (in-process exporters)
|
||||
> and cannot be used when `useCollector` is `true`. If both are enabled,
|
||||
> telemetry will be disabled.
|
||||
|
||||
3. Ensure your account or service account has these IAM roles:
|
||||
- Cloud Trace Agent
|
||||
|
||||
+12
-8
@@ -36,9 +36,11 @@ using the `/theme` command within Gemini CLI:
|
||||
preview or highlight as you select.
|
||||
4. Confirm your selection to apply the theme.
|
||||
|
||||
**Note:** If a theme is defined in your `settings.json` file (either by name or
|
||||
by a file path), you must remove the `"theme"` setting from the file before you
|
||||
can change the theme using the `/theme` command.
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> If a theme is defined in your `settings.json` file (either by name or
|
||||
> by a file path), you must remove the `"theme"` setting from the file before
|
||||
> you can change the theme using the `/theme` command.
|
||||
|
||||
### Theme persistence
|
||||
|
||||
@@ -179,11 +181,13 @@ custom theme defined in `settings.json`.
|
||||
}
|
||||
```
|
||||
|
||||
**Security note:** For your safety, Gemini CLI will only load theme files that
|
||||
are located within your home directory. If you attempt to load a theme from
|
||||
outside your home directory, a warning will be displayed and the theme will not
|
||||
be loaded. This is to prevent loading potentially malicious theme files from
|
||||
untrusted sources.
|
||||
<!-- prettier-ignore -->
|
||||
> [!WARNING]
|
||||
> For your safety, Gemini CLI will only load theme files that
|
||||
> are located within your home directory. If you attempt to load a theme from
|
||||
> outside your home directory, a warning will be displayed and the theme will
|
||||
> not be loaded. This is to prevent loading potentially malicious theme files
|
||||
> from untrusted sources.
|
||||
|
||||
### Example custom theme
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ create files, and control what Gemini CLI can see.
|
||||
## Prerequisites
|
||||
|
||||
- Gemini CLI installed and authenticated.
|
||||
- A project directory to work with (e.g., a git repository).
|
||||
- A project directory to work with (for example, a git repository).
|
||||
|
||||
## How to give the agent context (Reading files)
|
||||
## Providing context by reading files
|
||||
|
||||
Gemini CLI will generally try to read relevant files, sometimes prompting you
|
||||
for access (depending on your settings). To ensure that Gemini CLI uses a file,
|
||||
@@ -58,11 +58,13 @@ You know there's a `UserProfile` component, but you don't know where it lives.
|
||||
```
|
||||
|
||||
Gemini uses the `glob` or `list_directory` tools to search your project
|
||||
structure. It will return the specific path (e.g.,
|
||||
structure. It will return the specific path (for example,
|
||||
`src/components/UserProfile.tsx`), which you can then use with `@` in your next
|
||||
turn.
|
||||
|
||||
> **Tip:** You can also ask for lists of files, like "Show me all the TypeScript
|
||||
<!-- prettier-ignore -->
|
||||
> [!TIP]
|
||||
> You can also ask for lists of files, like "Show me all the TypeScript
|
||||
> configuration files in the root directory."
|
||||
|
||||
## How to modify code
|
||||
@@ -111,8 +113,8 @@ or, better yet, run your project's tests.
|
||||
`Run the tests for the UserProfile component.`
|
||||
```
|
||||
|
||||
Gemini CLI uses the `run_shell_command` tool to execute your test runner (e.g.,
|
||||
`npm test` or `jest`). This ensures the changes didn't break existing
|
||||
Gemini CLI uses the `run_shell_command` tool to execute your test runner (for
|
||||
example, `npm test` or `jest`). This ensures the changes didn't break existing
|
||||
functionality.
|
||||
|
||||
## Advanced: Controlling what Gemini sees
|
||||
|
||||
@@ -62,8 +62,10 @@ You tell Gemini about new servers by editing your `settings.json`.
|
||||
}
|
||||
```
|
||||
|
||||
> **Note:** The `command` is `docker`, and the rest are arguments passed to it.
|
||||
> We map the local environment variable into the container so your secret isn't
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> The `command` is `docker`, and the rest are arguments passed to it. We
|
||||
> map the local environment variable into the container so your secret isn't
|
||||
> hardcoded in the config file.
|
||||
|
||||
## How to verify the connection
|
||||
|
||||
@@ -11,8 +11,8 @@ persistent facts, and inspect the active context.
|
||||
|
||||
## Why manage context?
|
||||
|
||||
Out of the box, Gemini CLI is smart but generic. It doesn't know your preferred
|
||||
testing framework, your indentation style, or that you hate using `any` in
|
||||
Gemini CLI is powerful but general. It doesn't know your preferred testing
|
||||
framework, your indentation style, or your preference against `any` in
|
||||
TypeScript. Context management solves this by giving the agent persistent
|
||||
memory.
|
||||
|
||||
@@ -109,11 +109,11 @@ immediately. Force a reload with:
|
||||
|
||||
## Best practices
|
||||
|
||||
- **Keep it focused:** Don't dump your entire internal wiki into `GEMINI.md`.
|
||||
Keep instructions actionable and relevant to code generation.
|
||||
- **Keep it focused:** Avoid adding excessive content to `GEMINI.md`. Keep
|
||||
instructions actionable and relevant to code generation.
|
||||
- **Use negative constraints:** Explicitly telling the agent what _not_ to do
|
||||
(e.g., "Do not use class components") is often more effective than vague
|
||||
positive instructions.
|
||||
(for example, "Do not use class components") is often more effective than
|
||||
vague positive instructions.
|
||||
- **Review often:** Periodically check your `GEMINI.md` files to remove outdated
|
||||
rules.
|
||||
|
||||
|
||||
@@ -5,9 +5,10 @@ structured environment with model steering's real-time feedback, you can guide
|
||||
Gemini CLI through the research and design phases to ensure the final
|
||||
implementation plan is exactly what you need.
|
||||
|
||||
> **Note:** This is a preview feature under active development. Preview features
|
||||
> may only be available in the **Preview** channel or may need to be enabled
|
||||
> under `/settings`.
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> This is an experimental feature currently under active development and
|
||||
> may need to be enabled under `/settings`.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ automate complex workflows, and manage background processes safely.
|
||||
## Prerequisites
|
||||
|
||||
- Gemini CLI installed and authenticated.
|
||||
- Basic familiarity with your system's shell (Bash, Zsh, PowerShell, etc.).
|
||||
- Basic familiarity with your system's shell (Bash, Zsh, PowerShell, and so on).
|
||||
|
||||
## How to run commands directly (`!`)
|
||||
|
||||
@@ -49,7 +49,7 @@ You want to run tests and fix any failures.
|
||||
6. Gemini uses `replace` to fix the bug.
|
||||
7. Gemini runs `npm test` again to verify the fix.
|
||||
|
||||
This loop turns Gemini into an autonomous engineer.
|
||||
This loop lets Gemini work autonomously.
|
||||
|
||||
## How to manage background processes
|
||||
|
||||
@@ -75,7 +75,7 @@ confirmation prompts) by streaming the output to you. However, for highly
|
||||
interactive tools (like `vim` or `top`), it's often better to run them yourself
|
||||
in a separate terminal window or use the `!` prefix.
|
||||
|
||||
## Safety first
|
||||
## Safety features
|
||||
|
||||
Giving an AI access to your shell is powerful but risky. Gemini CLI includes
|
||||
several safety layers.
|
||||
|
||||
@@ -10,7 +10,9 @@ agents in the following repositories:
|
||||
- [ADK Samples (Python)](https://github.com/google/adk-samples/tree/main/python)
|
||||
- [ADK Python Contributing Samples](https://github.com/google/adk-python/tree/main/contributing/samples)
|
||||
|
||||
> **Note: Remote subagents are currently an experimental feature.**
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> Remote subagents are currently an experimental feature.
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -82,7 +84,8 @@ Markdown file.
|
||||
---
|
||||
```
|
||||
|
||||
> **Note:** Mixed local and remote agents, or multiple local agents, are not
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE] Mixed local and remote agents, or multiple local agents, are not
|
||||
> supported in a single file; the list format is currently remote-only.
|
||||
|
||||
## Authentication
|
||||
@@ -362,5 +365,7 @@ Users can manage subagents using the following commands within the Gemini CLI:
|
||||
- `/agents enable <agent_name>`: Enables a specific subagent.
|
||||
- `/agents disable <agent_name>`: Disables a specific subagent.
|
||||
|
||||
> **Tip:** You can use the `@cli_help` agent within Gemini CLI for assistance
|
||||
<!-- prettier-ignore -->
|
||||
> [!TIP]
|
||||
> You can use the `@cli_help` agent within Gemini CLI for assistance
|
||||
> with configuring subagents.
|
||||
|
||||
+21
-13
@@ -5,16 +5,18 @@ session. They are designed to handle specific, complex tasks—like deep codebas
|
||||
analysis, documentation lookup, or domain-specific reasoning—without cluttering
|
||||
the main agent's context or toolset.
|
||||
|
||||
> **Note: Subagents are currently an experimental feature.**
|
||||
>
|
||||
> To use custom subagents, you must ensure they are enabled in your
|
||||
> `settings.json` (enabled by default):
|
||||
>
|
||||
> ```json
|
||||
> {
|
||||
> "experimental": { "enableAgents": true }
|
||||
> }
|
||||
> ```
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> Subagents are currently an experimental feature.
|
||||
>
|
||||
To use custom subagents, you must ensure they are enabled in your
|
||||
`settings.json` (enabled by default):
|
||||
|
||||
```json
|
||||
{
|
||||
"experimental": { "enableAgents": true }
|
||||
}
|
||||
```
|
||||
|
||||
## What are subagents?
|
||||
|
||||
@@ -114,7 +116,9 @@ Gemini CLI comes with the following built-in subagents:
|
||||
the pricing table from this page," "Click the login button and enter my
|
||||
credentials."
|
||||
|
||||
> **Note:** This is a preview feature currently under active development.
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> This is a preview feature currently under active development.
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
@@ -217,7 +221,9 @@ captures a screenshot and sends it to the vision model for analysis. The model
|
||||
returns coordinates and element descriptions that the browser agent uses with
|
||||
the `click_at` tool for precise, coordinate-based interactions.
|
||||
|
||||
> **Note:** The visual agent requires API key or Vertex AI authentication. It is
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> The visual agent requires API key or Vertex AI authentication. It is
|
||||
> not available when using "Sign in with Google".
|
||||
|
||||
## Creating custom subagents
|
||||
@@ -405,7 +411,9 @@ that your subagent was called with a specific prompt and the given description.
|
||||
Gemini CLI can also delegate tasks to remote subagents using the Agent-to-Agent
|
||||
(A2A) protocol.
|
||||
|
||||
> **Note: Remote subagents are currently an experimental feature.**
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> Remote subagents are currently an experimental feature.
|
||||
|
||||
See the [Remote Subagents documentation](remote-agents) for detailed
|
||||
configuration, authentication, and usage instructions.
|
||||
|
||||
@@ -234,7 +234,9 @@ skill definitions in a `skills/` directory. For example,
|
||||
|
||||
### Sub-agents
|
||||
|
||||
> **Note:** Sub-agents are a preview feature currently under active development.
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> Sub-agents are a preview feature currently under active development.
|
||||
|
||||
Provide [sub-agents](../core/subagents.md) that users can delegate tasks to. Add
|
||||
agent definition files (`.md`) to an `agents/` directory in your extension root.
|
||||
@@ -253,7 +255,9 @@ Rules contributed by extensions run in their own tier (tier 2), alongside
|
||||
workspace-defined policies. This tier has higher priority than the default rules
|
||||
but lower priority than user or admin policies.
|
||||
|
||||
> **Warning:** For security, Gemini CLI ignores any `allow` decisions or `yolo`
|
||||
<!-- prettier-ignore -->
|
||||
> [!WARNING]
|
||||
> For security, Gemini CLI ignores any `allow` decisions or `yolo`
|
||||
> mode configurations in extension policies. This ensures that an extension
|
||||
> cannot automatically approve tool calls or bypass security measures without
|
||||
> your confirmation.
|
||||
|
||||
@@ -4,7 +4,9 @@ To use Gemini CLI, you'll need to authenticate with Google. This guide helps you
|
||||
quickly find the best way to sign in based on your account type and how you're
|
||||
using the CLI.
|
||||
|
||||
> **Note:** Looking for a high-level comparison of all available subscriptions?
|
||||
<!-- prettier-ignore -->
|
||||
> [!TIP]
|
||||
> Looking for a high-level comparison of all available subscriptions?
|
||||
> To compare features and find the right quota for your needs, see our
|
||||
> [Plans page](https://geminicli.com/plans/).
|
||||
|
||||
@@ -40,11 +42,11 @@ Select the authentication method that matches your situation in the table below:
|
||||
|
||||
If you run Gemini CLI on your local machine, the simplest authentication method
|
||||
is logging in with your Google account. This method requires a web browser on a
|
||||
machine that can communicate with the terminal running Gemini CLI (e.g., your
|
||||
local machine).
|
||||
machine that can communicate with the terminal running Gemini CLI (for example,
|
||||
your local machine).
|
||||
|
||||
> **Important:** If you are a **Google AI Pro** or **Google AI Ultra**
|
||||
> subscriber, use the Google account associated with your subscription.
|
||||
If you are a **Google AI Pro** or **Google AI Ultra** subscriber, use the Google
|
||||
account associated with your subscription.
|
||||
|
||||
To authenticate and use Gemini CLI:
|
||||
|
||||
@@ -107,7 +109,9 @@ To authenticate and use Gemini CLI with a Gemini API key:
|
||||
|
||||
4. Select **Use Gemini API key**.
|
||||
|
||||
> **Warning:** Treat API keys, especially for services like Gemini, as sensitive
|
||||
<!-- prettier-ignore -->
|
||||
> [!WARNING]
|
||||
> Treat API keys, especially for services like Gemini, as sensitive
|
||||
> credentials. Protect them to prevent unauthorized access and potential misuse
|
||||
> of the service under your account.
|
||||
|
||||
@@ -130,7 +134,7 @@ For example:
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
# Replace with your project ID and desired location (e.g., us-central1)
|
||||
# Replace with your project ID and desired location (for example, us-central1)
|
||||
export GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"
|
||||
export GOOGLE_CLOUD_LOCATION="YOUR_PROJECT_LOCATION"
|
||||
```
|
||||
@@ -138,7 +142,7 @@ export GOOGLE_CLOUD_LOCATION="YOUR_PROJECT_LOCATION"
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
# Replace with your project ID and desired location (e.g., us-central1)
|
||||
# Replace with your project ID and desired location (for example, us-central1)
|
||||
$env:GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"
|
||||
$env:GOOGLE_CLOUD_LOCATION="YOUR_PROJECT_LOCATION"
|
||||
```
|
||||
@@ -150,20 +154,20 @@ To make any Vertex AI environment variable settings persistent, see
|
||||
|
||||
Consider this authentication method if you have Google Cloud CLI installed.
|
||||
|
||||
> **Note:** If you have previously set `GOOGLE_API_KEY` or `GEMINI_API_KEY`, you
|
||||
> must unset them to use ADC:
|
||||
>
|
||||
> **macOS/Linux**
|
||||
>
|
||||
> ```bash
|
||||
> unset GOOGLE_API_KEY GEMINI_API_KEY
|
||||
> ```
|
||||
>
|
||||
> **Windows (PowerShell)**
|
||||
>
|
||||
> ```powershell
|
||||
> Remove-Item Env:\GOOGLE_API_KEY, Env:\GEMINI_API_KEY -ErrorAction Ignore
|
||||
> ```
|
||||
If you have previously set `GOOGLE_API_KEY` or `GEMINI_API_KEY`, you must unset
|
||||
them to use ADC.
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
unset GOOGLE_API_KEY GEMINI_API_KEY
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
Remove-Item Env:\GOOGLE_API_KEY, Env:\GEMINI_API_KEY -ErrorAction Ignore
|
||||
```
|
||||
|
||||
1. Verify you have a Google Cloud project and Vertex AI API is enabled.
|
||||
|
||||
@@ -188,20 +192,20 @@ Consider this authentication method if you have Google Cloud CLI installed.
|
||||
Consider this method of authentication in non-interactive environments, CI/CD
|
||||
pipelines, or if your organization restricts user-based ADC or API key creation.
|
||||
|
||||
> **Note:** If you have previously set `GOOGLE_API_KEY` or `GEMINI_API_KEY`, you
|
||||
> must unset them:
|
||||
>
|
||||
> **macOS/Linux**
|
||||
>
|
||||
> ```bash
|
||||
> unset GOOGLE_API_KEY GEMINI_API_KEY
|
||||
> ```
|
||||
>
|
||||
> **Windows (PowerShell)**
|
||||
>
|
||||
> ```powershell
|
||||
> Remove-Item Env:\GOOGLE_API_KEY, Env:\GEMINI_API_KEY -ErrorAction Ignore
|
||||
> ```
|
||||
If you have previously set `GOOGLE_API_KEY` or `GEMINI_API_KEY`, you must unset
|
||||
them:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
unset GOOGLE_API_KEY GEMINI_API_KEY
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
Remove-Item Env:\GOOGLE_API_KEY, Env:\GEMINI_API_KEY -ErrorAction Ignore
|
||||
```
|
||||
|
||||
1. [Create a service account and key](https://cloud.google.com/iam/docs/keys-create-delete)
|
||||
and download the provided JSON file. Assign the "Vertex AI User" role to the
|
||||
@@ -233,8 +237,11 @@ pipelines, or if your organization restricts user-based ADC or API key creation.
|
||||
```
|
||||
|
||||
5. Select **Vertex AI**.
|
||||
> **Warning:** Protect your service account key file as it gives access to
|
||||
> your resources.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
> [!WARNING]
|
||||
> Protect your service account key file as it gives access to
|
||||
> your resources.
|
||||
|
||||
#### C. Vertex AI - Google Cloud API key
|
||||
|
||||
@@ -257,10 +264,9 @@ pipelines, or if your organization restricts user-based ADC or API key creation.
|
||||
$env:GOOGLE_API_KEY="YOUR_GOOGLE_API_KEY"
|
||||
```
|
||||
|
||||
> **Note:** If you see errors like
|
||||
> `"API keys are not supported by this API..."`, your organization might
|
||||
> restrict API key usage for this service. Try the other Vertex AI
|
||||
> authentication methods instead.
|
||||
If you see errors like `"API keys are not supported by this API..."`, your
|
||||
organization might restrict API key usage for this service. Try the other
|
||||
Vertex AI authentication methods instead.
|
||||
|
||||
3. [Configure your Google Cloud Project](#set-gcp).
|
||||
|
||||
@@ -274,7 +280,9 @@ pipelines, or if your organization restricts user-based ADC or API key creation.
|
||||
|
||||
## Set your Google Cloud project <a id="set-gcp"></a>
|
||||
|
||||
> **Important:** Most individual Google accounts (free and paid) don't require a
|
||||
<!-- prettier-ignore -->
|
||||
> [!IMPORTANT]
|
||||
> Most individual Google accounts (free and paid) don't require a
|
||||
> Google Cloud project for authentication.
|
||||
|
||||
When you sign in using your Google account, you may need to configure a Google
|
||||
@@ -325,29 +333,31 @@ persist them with the following methods:
|
||||
1. **Add your environment variables to your shell configuration file:** Append
|
||||
the environment variable commands to your shell's startup file.
|
||||
|
||||
**macOS/Linux** (e.g., `~/.bashrc`, `~/.zshrc`, or `~/.profile`):
|
||||
**macOS/Linux** (for example, `~/.bashrc`, `~/.zshrc`, or `~/.profile`):
|
||||
|
||||
```bash
|
||||
echo 'export GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"' >> ~/.bashrc
|
||||
source ~/.bashrc
|
||||
```
|
||||
|
||||
**Windows (PowerShell)** (e.g., `$PROFILE`):
|
||||
**Windows (PowerShell)** (for example, `$PROFILE`):
|
||||
|
||||
```powershell
|
||||
Add-Content -Path $PROFILE -Value '$env:GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"'
|
||||
. $PROFILE
|
||||
```
|
||||
|
||||
> **Warning:** Be aware that when you export API keys or service account
|
||||
> paths in your shell configuration file, any process launched from that
|
||||
> shell can read them.
|
||||
<!-- prettier-ignore -->
|
||||
> [!WARNING]
|
||||
> Be aware that when you export API keys or service account
|
||||
> paths in your shell configuration file, any process launched from that
|
||||
> shell can read them.
|
||||
|
||||
2. **Use a `.env` file:** Create a `.gemini/.env` file in your project
|
||||
directory or home directory. Gemini CLI automatically loads variables from
|
||||
the first `.env` file it finds, searching up from the current directory,
|
||||
then in your home directory's `.gemini/.env` (e.g., `~/.gemini/.env` or
|
||||
`%USERPROFILE%\.gemini\.env`).
|
||||
then in your home directory's `.gemini/.env` (for example, `~/.gemini/.env`
|
||||
or `%USERPROFILE%\.gemini\.env`).
|
||||
|
||||
Example for user-wide settings:
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@ Gemini CLI helps you automate common engineering tasks by combining AI reasoning
|
||||
with local system tools. This document provides examples of how to use the CLI
|
||||
for file management, code analysis, and data transformation.
|
||||
|
||||
> **Note:** These examples demonstrate potential capabilities. Your actual
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> These examples demonstrate potential capabilities. Your actual
|
||||
> results can vary based on the model used and your project environment.
|
||||
|
||||
## Rename your photographs based on content
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
Gemini 3 Pro and Gemini 3 Flash are available on Gemini CLI for all users!
|
||||
|
||||
> **Note:** Gemini 3.1 Pro Preview is rolling out. To determine whether you have
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> Gemini 3.1 Pro Preview is rolling out. To determine whether you have
|
||||
> access to Gemini 3.1, use the `/model` command and select **Manual**. If you
|
||||
> have access, you will see `gemini-3.1-pro-preview`.
|
||||
>
|
||||
@@ -25,7 +27,7 @@ Get started by upgrading Gemini CLI to the latest version:
|
||||
npm install -g @google/gemini-cli@latest
|
||||
```
|
||||
|
||||
After you’ve confirmed your version is 0.21.1 or later:
|
||||
If your version is 0.21.1 or later:
|
||||
|
||||
1. Run `/model`.
|
||||
2. Select **Auto (Gemini 3)**.
|
||||
@@ -39,7 +41,9 @@ When you encounter that limit, you’ll be given the option to switch to Gemini
|
||||
2.5 Pro, upgrade for higher limits, or stop. You’ll also be told when your usage
|
||||
limit resets and Gemini 3 Pro can be used again.
|
||||
|
||||
> **Note:** Looking to upgrade for higher limits? To compare subscription
|
||||
<!-- prettier-ignore -->
|
||||
> [!TIP]
|
||||
> Looking to upgrade for higher limits? To compare subscription
|
||||
> options and find the right quota for your needs, see our
|
||||
> [Plans page](https://geminicli.com/plans/).
|
||||
|
||||
@@ -52,7 +56,9 @@ There may be times when the Gemini 3 Pro model is overloaded. When that happens,
|
||||
Gemini CLI will ask you to decide whether you want to keep trying Gemini 3 Pro
|
||||
or fallback to Gemini 2.5 Pro.
|
||||
|
||||
> **Note:** The **Keep trying** option uses exponential backoff, in which Gemini
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> The **Keep trying** option uses exponential backoff, in which Gemini
|
||||
> CLI waits longer between each retry, when the system is busy. If the retry
|
||||
> doesn't happen immediately, please wait a few minutes for the request to
|
||||
> process.
|
||||
@@ -109,7 +115,7 @@ then:
|
||||
|
||||
Restart Gemini CLI and you should have access to Gemini 3.
|
||||
|
||||
## Need help?
|
||||
## Next steps
|
||||
|
||||
If you need help, we recommend searching for an existing
|
||||
[GitHub issue](https://github.com/google-gemini/gemini-cli/issues). If you
|
||||
|
||||
+3
-1
@@ -143,7 +143,9 @@ Hooks are executed with a sanitized environment.
|
||||
|
||||
## Security and risks
|
||||
|
||||
> **Warning: Hooks execute arbitrary code with your user privileges.** By
|
||||
<!-- prettier-ignore -->
|
||||
> [!WARNING]
|
||||
> Hooks execute arbitrary code with your user privileges. By
|
||||
> configuring hooks, you are allowing scripts to run shell commands on your
|
||||
> machine.
|
||||
|
||||
|
||||
@@ -132,9 +132,11 @@ to the CLI whenever the user's context changes.
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** The `openFiles` list should only include files that exist on disk.
|
||||
Virtual files (e.g., unsaved files without a path, editor settings pages)
|
||||
**MUST** be excluded.
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> The `openFiles` list should only include files that exist on disk.
|
||||
> Virtual files (e.g., unsaved files without a path, editor settings pages)
|
||||
> **MUST** be excluded.
|
||||
|
||||
### How the CLI uses this context
|
||||
|
||||
|
||||
@@ -66,9 +66,11 @@ You can also install the extension directly from a marketplace.
|
||||
Follow your editor's instructions for installing extensions from this
|
||||
registry.
|
||||
|
||||
> NOTE: The "Gemini CLI Companion" extension may appear towards the bottom of
|
||||
> search results. If you don't see it immediately, try scrolling down or sorting
|
||||
> by "Newly Published".
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> The "Gemini CLI Companion" extension may appear towards the bottom of
|
||||
> search results. If you don't see it immediately, try scrolling down or
|
||||
> sorting by "Newly Published".
|
||||
>
|
||||
> After manually installing the extension, you must run `/ide enable` in the CLI
|
||||
> to activate the integration.
|
||||
@@ -103,7 +105,9 @@ IDE, run:
|
||||
If connected, this command will show the IDE it's connected to and a list of
|
||||
recently opened files it is aware of.
|
||||
|
||||
> [!NOTE] The file list is limited to 10 recently accessed files within your
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> The file list is limited to 10 recently accessed files within your
|
||||
> workspace and only includes local files on disk.)
|
||||
|
||||
### Working with diffs
|
||||
|
||||
+50
-116
@@ -1,8 +1,14 @@
|
||||
# Gemini CLI documentation
|
||||
# Welcome to Gemini CLI
|
||||
|
||||
Gemini CLI brings the power of Gemini models directly into your terminal. Use it
|
||||
to understand code, automate tasks, and build workflows with your local project
|
||||
context.
|
||||
Unleash the power of Gemini models directly in your terminal. Gemini CLI is your
|
||||
intelligent assistant for coding, automation, and understanding complex
|
||||
projects. It seamlessly integrates with your local development environment,
|
||||
empowering you to accelerate workflows, tackle challenging tasks, and explore
|
||||
codebases with unprecedented ease.
|
||||
|
||||
Whether you're refactoring code, debugging issues, or orchestrating complex
|
||||
automation, Gemini CLI works alongside you, transforming your terminal into a
|
||||
highly productive AI-powered workspace.
|
||||
|
||||
## Install
|
||||
|
||||
@@ -10,132 +16,60 @@ context.
|
||||
npm install -g @google/gemini-cli
|
||||
```
|
||||
|
||||
## Get started
|
||||
## Get Started in Minutes
|
||||
|
||||
Jump in to Gemini CLI.
|
||||
Ready to boost your productivity? You can get Gemini CLI up and running right
|
||||
away.
|
||||
|
||||
- **[Quickstart](./get-started/index.md):** Your first session with Gemini CLI.
|
||||
- **[Installation](./get-started/installation.md):** How to install Gemini CLI
|
||||
on your system.
|
||||
- **[Authentication](./get-started/authentication.md):** Setup instructions for
|
||||
personal and enterprise accounts.
|
||||
- **[Examples](./get-started/examples.md):** Practical examples of Gemini CLI in
|
||||
action.
|
||||
- **[CLI cheatsheet](./cli/cli-reference.md):** A quick reference for common
|
||||
commands and options.
|
||||
- **[Gemini 3 on Gemini CLI](./get-started/gemini-3.md):** Learn about Gemini 3
|
||||
support in Gemini CLI.
|
||||
→ Dive into the [Quickstart Guide](./get-started/index.md) for your first
|
||||
interactive session.
|
||||
|
||||
## Use Gemini CLI
|
||||
## What Can Gemini CLI Do For You?
|
||||
|
||||
User-focused guides and tutorials for daily development workflows.
|
||||
Gemini CLI isn't just a chatbot; it's a powerful agent capable of executing a
|
||||
wide range of tasks directly within your project context.
|
||||
|
||||
- **[File management](./cli/tutorials/file-management.md):** How to work with
|
||||
local files and directories.
|
||||
- **[Get started with Agent skills](./cli/tutorials/skills-getting-started.md):**
|
||||
Getting started with specialized expertise.
|
||||
- **[Manage context and memory](./cli/tutorials/memory-management.md):**
|
||||
Managing persistent instructions and facts.
|
||||
- **[Execute shell commands](./cli/tutorials/shell-commands.md):** Executing
|
||||
system commands safely.
|
||||
- **[Manage sessions and history](./cli/tutorials/session-management.md):**
|
||||
Resuming, managing, and rewinding conversations.
|
||||
- **[Plan tasks with todos](./cli/tutorials/task-planning.md):** Using todos for
|
||||
complex workflows.
|
||||
- **[Web search and fetch](./cli/tutorials/web-tools.md):** Searching and
|
||||
fetching content from the web.
|
||||
- **[Set up an MCP server](./cli/tutorials/mcp-setup.md):** Set up an MCP
|
||||
server.
|
||||
- **[Automate tasks](./cli/tutorials/automation.md):** Automate tasks.
|
||||
### Automate Code Refactoring and Generation
|
||||
|
||||
## Features
|
||||
Tired of repetitive coding tasks? Let Gemini CLI handle them. It can understand
|
||||
your existing code to generate new features or refactor complex sections with
|
||||
ease.
|
||||
|
||||
Technical documentation for each capability of Gemini CLI.
|
||||
→ Learn how to efficiently [Modify Code](./cli/tutorials/file-management.md).
|
||||
|
||||
- **[Extensions](./extensions/index.md):** Extend Gemini CLI with new tools and
|
||||
capabilities.
|
||||
- **[Agent Skills](./cli/skills.md):** Use specialized agents for specific
|
||||
tasks.
|
||||
- **[Checkpointing](./cli/checkpointing.md):** Automatic session snapshots.
|
||||
- **[Headless mode](./cli/headless.md):** Programmatic and scripting interface.
|
||||
- **[Hooks](./hooks/index.md):** Customize Gemini CLI behavior with scripts.
|
||||
- **[IDE integration](./ide-integration/index.md):** Integrate Gemini CLI with
|
||||
your favorite IDE.
|
||||
- **[MCP servers](./tools/mcp-server.md):** Connect to and use remote agents.
|
||||
- **[Model routing](./cli/model-routing.md):** Automatic fallback resilience.
|
||||
- **[Model selection](./cli/model.md):** Choose the best model for your needs.
|
||||
- **[Plan mode 🔬](./cli/plan-mode.md):** Use a safe, read-only mode for
|
||||
planning complex changes.
|
||||
- **[Subagents 🔬](./core/subagents.md):** Using specialized agents for specific
|
||||
tasks.
|
||||
- **[Remote subagents 🔬](./core/remote-agents.md):** Connecting to and using
|
||||
remote agents.
|
||||
- **[Rewind](./cli/rewind.md):** Rewind and replay sessions.
|
||||
- **[Sandboxing](./cli/sandbox.md):** Isolate tool execution.
|
||||
- **[Settings](./cli/settings.md):** Full configuration reference.
|
||||
- **[Telemetry](./cli/telemetry.md):** Usage and performance metric details.
|
||||
- **[Token caching](./cli/token-caching.md):** Performance optimization.
|
||||
### Understand Complex Codebases
|
||||
|
||||
## Configuration
|
||||
Navigate unfamiliar projects or quickly grasp new architectural patterns. Gemini
|
||||
CLI can analyze large code repositories, explaining their purpose, structure,
|
||||
and key functionalities.
|
||||
|
||||
Settings and customization options for Gemini CLI.
|
||||
→ Explore examples of how to
|
||||
[Explain a Repository by Reading its Code](./get-started/examples.md).
|
||||
|
||||
- **[Custom commands](./cli/custom-commands.md):** Personalized shortcuts.
|
||||
- **[Enterprise configuration](./cli/enterprise.md):** Professional environment
|
||||
controls.
|
||||
- **[Ignore files (.geminiignore)](./cli/gemini-ignore.md):** Exclusion pattern
|
||||
reference.
|
||||
- **[Model configuration](./cli/generation-settings.md):** Fine-tune generation
|
||||
parameters like temperature and thinking budget.
|
||||
- **[Project context (GEMINI.md)](./cli/gemini-md.md):** Technical hierarchy of
|
||||
context files.
|
||||
- **[System prompt override](./cli/system-prompt.md):** Instruction replacement
|
||||
logic.
|
||||
- **[Themes](./cli/themes.md):** UI personalization technical guide.
|
||||
- **[Trusted folders](./cli/trusted-folders.md):** Security permission logic.
|
||||
### Streamline Your Development Workflows
|
||||
|
||||
## Reference
|
||||
From planning tasks with TODOs to managing long-running sessions, Gemini CLI
|
||||
helps you orchestrate your entire development process, making complex workflows
|
||||
simpler and more efficient.
|
||||
|
||||
Deep technical documentation and API specifications.
|
||||
→ Discover how to [Plan Tasks with ToDos](./cli/tutorials/task-planning.md) or
|
||||
[Manage Sessions and History](./cli/tutorials/session-management.md).
|
||||
|
||||
- **[Command reference](./reference/commands.md):** Detailed slash command
|
||||
guide.
|
||||
- **[Configuration reference](./reference/configuration.md):** Settings and
|
||||
environment variables.
|
||||
- **[Keyboard shortcuts](./reference/keyboard-shortcuts.md):** Productivity
|
||||
tips.
|
||||
- **[Memory import processor](./reference/memport.md):** How Gemini CLI
|
||||
processes memory from various sources.
|
||||
- **[Policy engine](./reference/policy-engine.md):** Fine-grained execution
|
||||
control.
|
||||
- **[Tools reference](./reference/tools.md):** Information on how tools are
|
||||
defined, registered, and used.
|
||||
## Explore Key Features and Sections
|
||||
|
||||
## Resources
|
||||
Dive deeper into Gemini CLI's capabilities and comprehensive documentation:
|
||||
|
||||
Support, release history, and legal information.
|
||||
- **[Agent Skills](./cli/skills.md):** Extend Gemini CLI's intelligence with
|
||||
specialized expertise for any domain or task.
|
||||
- **[Extensions](./extensions/index.md):** Customize and enhance your CLI
|
||||
experience by building or installing new tools and functionalities.
|
||||
- **[Configuration](./reference/configuration.md):** Fine-tune Gemini CLI to
|
||||
match your preferences and project requirements with detailed settings and
|
||||
options.
|
||||
- **[Command Reference](./reference/commands.md):** A complete guide to all
|
||||
available CLI commands, flags, and interactive prompts.
|
||||
- **[Resources](./resources/faq.md):** Find answers to frequently asked
|
||||
questions, troubleshooting tips, and support information.
|
||||
|
||||
- **[FAQ](./resources/faq.md):** Answers to frequently asked questions.
|
||||
- **[Quota and pricing](./resources/quota-and-pricing.md):** Limits and billing
|
||||
details.
|
||||
- **[Terms and privacy](./resources/tos-privacy.md):** Official notices and
|
||||
terms.
|
||||
- **[Troubleshooting](./resources/troubleshooting.md):** Common issues and
|
||||
solutions.
|
||||
- **[Uninstall](./resources/uninstall.md):** How to uninstall Gemini CLI.
|
||||
|
||||
## Development
|
||||
|
||||
- **[Contribution guide](/docs/contributing):** How to contribute to Gemini CLI.
|
||||
- **[Integration testing](./integration-tests.md):** Running integration tests.
|
||||
- **[Issue and PR automation](./issue-and-pr-automation.md):** Automation for
|
||||
issues and pull requests.
|
||||
- **[Local development](./local-development.md):** Setting up a local
|
||||
development environment.
|
||||
- **[NPM package structure](./npm.md):** The structure of the NPM packages.
|
||||
|
||||
## Releases
|
||||
|
||||
- **[Release notes](./changelogs/index.md):** Release notes for all versions.
|
||||
- **[Stable release](./changelogs/latest.md):** The latest stable release.
|
||||
- **[Preview release](./changelogs/preview.md):** The latest preview release.
|
||||
We're excited for you to experience a new level of terminal productivity with
|
||||
Gemini CLI!
|
||||
|
||||
@@ -14,7 +14,9 @@ feature), while the PR is the "how" (the implementation). This separation helps
|
||||
us track work, prioritize features, and maintain clear historical context. Our
|
||||
automation is built around this principle.
|
||||
|
||||
> **Note:** Issues tagged as "🔒Maintainers only" are reserved for project
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> Issues tagged as "🔒Maintainers only" are reserved for project
|
||||
> maintainers. We will not accept pull requests related to these issues.
|
||||
|
||||
---
|
||||
|
||||
@@ -79,7 +79,9 @@ You can view traces in the Jaeger UI for local development.
|
||||
You can use an OpenTelemetry collector to forward telemetry data to Google Cloud
|
||||
Trace for custom processing or routing.
|
||||
|
||||
> **Warning:** Ensure you complete the
|
||||
<!-- prettier-ignore -->
|
||||
> [!WARNING]
|
||||
> Ensure you complete the
|
||||
> [Google Cloud telemetry prerequisites](./cli/telemetry.md#prerequisites)
|
||||
> (Project ID, authentication, IAM roles, and APIs) before using this method.
|
||||
|
||||
|
||||
@@ -60,8 +60,8 @@ Slash commands provide meta-level control over the CLI itself.
|
||||
- `list` (selecting this opens the auto-saved session browser)
|
||||
- `-- checkpoints --`
|
||||
- `list`, `save`, `resume`, `delete`, `share` (manual tagged checkpoints)
|
||||
- **Note:** Unique prefixes (for example `/cha` or `/resum`) resolve to the
|
||||
same grouped menu.
|
||||
- Unique prefixes (for example `/cha` or `/resu`) resolve to the same grouped
|
||||
menu.
|
||||
- **Sub-commands:**
|
||||
- **`debug`**
|
||||
- **Description:** Export the most recent API request as a JSON payload.
|
||||
|
||||
@@ -25,7 +25,9 @@ overridden by higher numbers):
|
||||
Gemini CLI uses JSON settings files for persistent configuration. There are four
|
||||
locations for these files:
|
||||
|
||||
> **Tip:** JSON-aware editors can use autocomplete and validation by pointing to
|
||||
<!-- prettier-ignore -->
|
||||
> [!TIP]
|
||||
> JSON-aware editors can use autocomplete and validation by pointing to
|
||||
> the generated schema at `schemas/settings.schema.json` in this repository.
|
||||
> When working outside the repo, reference the hosted schema at
|
||||
> `https://raw.githubusercontent.com/google-gemini/gemini-cli/main/schemas/settings.schema.json`.
|
||||
@@ -66,9 +68,9 @@ an environment variable `MY_API_TOKEN`, you could use it in `settings.json` like
|
||||
this: `"apiKey": "$MY_API_TOKEN"`. Additionally, each extension can have its own
|
||||
`.env` file in its directory, which will be loaded automatically.
|
||||
|
||||
> **Note for Enterprise Users:** For guidance on deploying and managing Gemini
|
||||
> CLI in a corporate environment, please see the
|
||||
> [Enterprise Configuration](../cli/enterprise.md) documentation.
|
||||
**Note for Enterprise Users:** For guidance on deploying and managing Gemini CLI
|
||||
in a corporate environment, please see the
|
||||
[Enterprise Configuration](../cli/enterprise.md) documentation.
|
||||
|
||||
### The `.gemini` directory in your project
|
||||
|
||||
@@ -684,6 +686,16 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
|
||||
```json
|
||||
{
|
||||
"gemini-3.1-flash-lite-preview": {
|
||||
"tier": "flash-lite",
|
||||
"family": "gemini-3",
|
||||
"isPreview": true,
|
||||
"isVisible": true,
|
||||
"features": {
|
||||
"thinking": false,
|
||||
"multimodalToolUse": true
|
||||
}
|
||||
},
|
||||
"gemini-3.1-pro-preview": {
|
||||
"tier": "pro",
|
||||
"family": "gemini-3",
|
||||
@@ -795,7 +807,7 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
"tier": "auto",
|
||||
"isPreview": true,
|
||||
"isVisible": true,
|
||||
"dialogDescription": "Let Gemini CLI decide the best model for the task: gemini-3.1-pro, gemini-3-flash",
|
||||
"dialogDescription": "Let Gemini CLI decide the best model for the task: gemini-3-pro, gemini-3-flash",
|
||||
"features": {
|
||||
"thinking": true,
|
||||
"multimodalToolUse": false
|
||||
@@ -824,6 +836,39 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
|
||||
```json
|
||||
{
|
||||
"gemini-3.1-pro-preview": {
|
||||
"default": "gemini-3.1-pro-preview",
|
||||
"contexts": [
|
||||
{
|
||||
"condition": {
|
||||
"hasAccessToPreview": false
|
||||
},
|
||||
"target": "gemini-2.5-pro"
|
||||
}
|
||||
]
|
||||
},
|
||||
"gemini-3.1-pro-preview-customtools": {
|
||||
"default": "gemini-3.1-pro-preview-customtools",
|
||||
"contexts": [
|
||||
{
|
||||
"condition": {
|
||||
"hasAccessToPreview": false
|
||||
},
|
||||
"target": "gemini-2.5-pro"
|
||||
}
|
||||
]
|
||||
},
|
||||
"gemini-3-flash-preview": {
|
||||
"default": "gemini-3-flash-preview",
|
||||
"contexts": [
|
||||
{
|
||||
"condition": {
|
||||
"hasAccessToPreview": false
|
||||
},
|
||||
"target": "gemini-2.5-flash"
|
||||
}
|
||||
]
|
||||
},
|
||||
"gemini-3-pro-preview": {
|
||||
"default": "gemini-3-pro-preview",
|
||||
"contexts": [
|
||||
@@ -995,6 +1040,132 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`modelConfigs.modelChains`** (object):
|
||||
- **Description:** Availability policy chains defining fallback behavior for
|
||||
models.
|
||||
- **Default:**
|
||||
|
||||
```json
|
||||
{
|
||||
"preview": [
|
||||
{
|
||||
"model": "gemini-3-pro-preview",
|
||||
"actions": {
|
||||
"terminal": "prompt",
|
||||
"transient": "prompt",
|
||||
"not_found": "prompt",
|
||||
"unknown": "prompt"
|
||||
},
|
||||
"stateTransitions": {
|
||||
"terminal": "terminal",
|
||||
"transient": "terminal",
|
||||
"not_found": "terminal",
|
||||
"unknown": "terminal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "gemini-3-flash-preview",
|
||||
"isLastResort": true,
|
||||
"actions": {
|
||||
"terminal": "prompt",
|
||||
"transient": "prompt",
|
||||
"not_found": "prompt",
|
||||
"unknown": "prompt"
|
||||
},
|
||||
"stateTransitions": {
|
||||
"terminal": "terminal",
|
||||
"transient": "terminal",
|
||||
"not_found": "terminal",
|
||||
"unknown": "terminal"
|
||||
}
|
||||
}
|
||||
],
|
||||
"default": [
|
||||
{
|
||||
"model": "gemini-2.5-pro",
|
||||
"actions": {
|
||||
"terminal": "prompt",
|
||||
"transient": "prompt",
|
||||
"not_found": "prompt",
|
||||
"unknown": "prompt"
|
||||
},
|
||||
"stateTransitions": {
|
||||
"terminal": "terminal",
|
||||
"transient": "terminal",
|
||||
"not_found": "terminal",
|
||||
"unknown": "terminal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "gemini-2.5-flash",
|
||||
"isLastResort": true,
|
||||
"actions": {
|
||||
"terminal": "prompt",
|
||||
"transient": "prompt",
|
||||
"not_found": "prompt",
|
||||
"unknown": "prompt"
|
||||
},
|
||||
"stateTransitions": {
|
||||
"terminal": "terminal",
|
||||
"transient": "terminal",
|
||||
"not_found": "terminal",
|
||||
"unknown": "terminal"
|
||||
}
|
||||
}
|
||||
],
|
||||
"lite": [
|
||||
{
|
||||
"model": "gemini-2.5-flash-lite",
|
||||
"actions": {
|
||||
"terminal": "silent",
|
||||
"transient": "silent",
|
||||
"not_found": "silent",
|
||||
"unknown": "silent"
|
||||
},
|
||||
"stateTransitions": {
|
||||
"terminal": "terminal",
|
||||
"transient": "terminal",
|
||||
"not_found": "terminal",
|
||||
"unknown": "terminal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "gemini-2.5-flash",
|
||||
"actions": {
|
||||
"terminal": "silent",
|
||||
"transient": "silent",
|
||||
"not_found": "silent",
|
||||
"unknown": "silent"
|
||||
},
|
||||
"stateTransitions": {
|
||||
"terminal": "terminal",
|
||||
"transient": "terminal",
|
||||
"not_found": "terminal",
|
||||
"unknown": "terminal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "gemini-2.5-pro",
|
||||
"isLastResort": true,
|
||||
"actions": {
|
||||
"terminal": "silent",
|
||||
"transient": "silent",
|
||||
"not_found": "silent",
|
||||
"unknown": "silent"
|
||||
},
|
||||
"stateTransitions": {
|
||||
"terminal": "terminal",
|
||||
"transient": "terminal",
|
||||
"not_found": "terminal",
|
||||
"unknown": "terminal"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- **Requires restart:** Yes
|
||||
|
||||
#### `agents`
|
||||
|
||||
- **`agents.overrides`** (object):
|
||||
@@ -1105,10 +1276,21 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
- **Description:** Legacy full-process sandbox execution environment. Set to a
|
||||
boolean to enable or disable the sandbox, provide a string path to a sandbox
|
||||
profile, or specify an explicit sandbox command (e.g., "docker", "podman",
|
||||
"lxc").
|
||||
"lxc", "windows-native").
|
||||
- **Default:** `undefined`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`tools.sandboxAllowedPaths`** (array):
|
||||
- **Description:** List of additional paths that the sandbox is allowed to
|
||||
access.
|
||||
- **Default:** `[]`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`tools.sandboxNetworkAccess`** (boolean):
|
||||
- **Description:** Whether the sandbox is allowed to access the network.
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`tools.shell.enableInteractiveShell`** (boolean):
|
||||
- **Description:** Use node-pty for an interactive shell experience. Fallback
|
||||
to child_process still applies.
|
||||
@@ -1431,6 +1613,13 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
- **Default:** `"gemma3-1b-gpu-custom"`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`experimental.memoryManager`** (boolean):
|
||||
- **Description:** Replace the built-in save_memory tool with a memory manager
|
||||
subagent that supports adding, removing, de-duplicating, and organizing
|
||||
memories.
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`experimental.topicUpdateNarration`** (boolean):
|
||||
- **Description:** Enable the experimental Topic & Update communication model
|
||||
for reduced chattiness and structured progress reporting.
|
||||
@@ -1539,7 +1728,11 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
- **Default:** `true`
|
||||
|
||||
- **`admin.mcp.config`** (object):
|
||||
- **Description:** Admin-configured MCP servers.
|
||||
- **Description:** Admin-configured MCP servers (allowlist).
|
||||
- **Default:** `{}`
|
||||
|
||||
- **`admin.mcp.requiredConfig`** (object):
|
||||
- **Description:** Admin-required MCP servers that are always injected.
|
||||
- **Default:** `{}`
|
||||
|
||||
- **`admin.skills.enabled`** (boolean):
|
||||
@@ -1559,7 +1752,9 @@ for compatibility. At least one of `command`, `url`, or `httpUrl` must be
|
||||
provided. If multiple are specified, the order of precedence is `httpUrl`, then
|
||||
`url`, then `command`.
|
||||
|
||||
> **Warning:** Avoid using underscores (`_`) in your server aliases (e.g., use
|
||||
<!-- prettier-ignore -->
|
||||
> [!WARNING]
|
||||
> Avoid using underscores (`_`) in your server aliases (e.g., use
|
||||
> `my-server` instead of `my_server`). The underlying policy engine parses Fully
|
||||
> Qualified Names (`mcp_server_tool`) using the first underscore after the
|
||||
> `mcp_` prefix. An underscore in your server alias will cause the parser to
|
||||
|
||||
@@ -113,7 +113,9 @@ There are three possible decisions a rule can enforce:
|
||||
- `ask_user`: The user is prompted to approve or deny the tool call. (In
|
||||
non-interactive mode, this is treated as `deny`.)
|
||||
|
||||
> **Note:** The `deny` decision is the recommended way to exclude tools. The
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> The `deny` decision is the recommended way to exclude tools. The
|
||||
> legacy `tools.exclude` setting in `settings.json` is deprecated in favor of
|
||||
> policy rules with a `deny` decision.
|
||||
|
||||
@@ -239,15 +241,17 @@ directory are **ignored**.
|
||||
- **Linux / macOS:** Must be owned by `root` (UID 0) and NOT writable by group
|
||||
or others (e.g., `chmod 755`).
|
||||
- **Windows:** Must be in `C:\ProgramData`. Standard users (`Users`, `Everyone`)
|
||||
must NOT have `Write`, `Modify`, or `Full Control` permissions. _Tip: If you
|
||||
see a security warning, use the folder properties to remove write permissions
|
||||
for non-admin groups. You may need to "Disable inheritance" in Advanced
|
||||
Security Settings._
|
||||
must NOT have `Write`, `Modify`, or `Full Control` permissions. If you see a
|
||||
security warning, use the folder properties to remove write permissions for
|
||||
non-admin groups. You may need to "Disable inheritance" in Advanced Security
|
||||
Settings.
|
||||
|
||||
**Note:** Supplemental admin policies (provided via `--admin-policy` or
|
||||
`adminPolicyPaths` settings) are **NOT** subject to these strict ownership
|
||||
checks, as they are explicitly provided by the user or administrator in their
|
||||
current execution context.
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> Supplemental admin policies (provided via `--admin-policy` or
|
||||
> `adminPolicyPaths` settings) are **NOT** subject to these strict ownership
|
||||
> checks, as they are explicitly provided by the user or administrator in their
|
||||
> current execution context.
|
||||
|
||||
### TOML rule schema
|
||||
|
||||
@@ -348,7 +352,9 @@ using the `mcpName` field. **This is the recommended approach** for defining MCP
|
||||
policies, as it is much more robust than manually writing Fully Qualified Names
|
||||
(FQNs) or string wildcards.
|
||||
|
||||
> **Warning:** Do not use underscores (`_`) in your MCP server names (e.g., use
|
||||
<!-- prettier-ignore -->
|
||||
> [!WARNING]
|
||||
> Do not use underscores (`_`) in your MCP server names (e.g., use
|
||||
> `my-server` rather than `my_server`). The policy parser splits Fully Qualified
|
||||
> Names (`mcp_server_tool`) on the _first_ underscore following the `mcp_`
|
||||
> prefix. If your server name contains an underscore, the parser will
|
||||
|
||||
@@ -95,7 +95,9 @@ For developers, the tool system is designed to be extensible and robust. The
|
||||
You can extend Gemini CLI with custom tools by configuring
|
||||
`tools.discoveryCommand` in your settings or by connecting to MCP servers.
|
||||
|
||||
> **Note:** For a deep dive into the internal Tool API and how to implement your
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> For a deep dive into the internal Tool API and how to implement your
|
||||
> own tools in the codebase, see the `packages/core/src/tools/` directory in
|
||||
> GitHub.
|
||||
|
||||
|
||||
@@ -21,9 +21,13 @@ All workflows in `.github/workflows/ci.yml` must pass on the `main` branch (for
|
||||
nightly) or the release branch (for preview/stable).
|
||||
|
||||
- **Platforms:** Tests must pass on **Linux and macOS**.
|
||||
- _Note:_ Windows tests currently run with `continue-on-error: true`. While a
|
||||
failure here doesn't block the release technically, it should be
|
||||
investigated.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> Windows tests currently run with `continue-on-error: true`. While a
|
||||
> failure here doesn't block the release technically, it should be
|
||||
> investigated.
|
||||
|
||||
- **Checks:**
|
||||
- **Linting:** No linting errors (ESLint, Prettier, etc.).
|
||||
- **Typechecking:** No TypeScript errors.
|
||||
|
||||
+11
-7
@@ -234,10 +234,12 @@ This workflow will automatically:
|
||||
Review the automatically created pull request(s) to ensure the cherry-pick was
|
||||
successful and the changes are correct. Once approved, merge the pull request.
|
||||
|
||||
**Security note:** The `release/*` branches are protected by branch protection
|
||||
rules. A pull request to one of these branches requires at least one review from
|
||||
a code owner before it can be merged. This ensures that no unauthorized code is
|
||||
released.
|
||||
<!-- prettier-ignore -->
|
||||
> [!WARNING]
|
||||
> The `release/*` branches are protected by branch protection
|
||||
> rules. A pull request to one of these branches requires at least one review from
|
||||
> a code owner before it can be merged. This ensures that no unauthorized code is
|
||||
> released.
|
||||
|
||||
#### 2.5. Adding multiple commits to a hotfix (advanced)
|
||||
|
||||
@@ -524,9 +526,11 @@ Notifications use
|
||||
[GitHub for Google Chat](https://workspace.google.com/marketplace/app/github_for_google_chat/536184076190).
|
||||
To modify the notifications, use `/github-settings` within the chat space.
|
||||
|
||||
> [!WARNING] The following instructions describe a fragile workaround that
|
||||
> depends on the internal structure of the chat application's UI. It is likely
|
||||
> to break with future updates.
|
||||
<!-- prettier-ignore -->
|
||||
> [!WARNING]
|
||||
> The following instructions describe a fragile workaround that depends on the
|
||||
> internal structure of the chat application's UI. It is likely to break with
|
||||
> future updates.
|
||||
|
||||
The list of available labels is not currently populated correctly. If you want
|
||||
to add a label that does not appear alphabetically in the first 30 labels in the
|
||||
|
||||
@@ -16,8 +16,10 @@ account.
|
||||
Your Gemini CLI Usage Statistics are handled in accordance with Google's Privacy
|
||||
Policy.
|
||||
|
||||
**Note:** See [quotas and pricing](quota-and-pricing.md) for the quota and
|
||||
pricing details that apply to your usage of the Gemini CLI.
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> See [quotas and pricing](quota-and-pricing.md) for the quota and
|
||||
> pricing details that apply to your usage of the Gemini CLI.
|
||||
|
||||
## Supported authentication methods
|
||||
|
||||
|
||||
@@ -187,5 +187,7 @@ guide_, consider searching the Gemini CLI
|
||||
If you can't find an issue similar to yours, consider creating a new GitHub
|
||||
Issue with a detailed description. Pull requests are also welcome!
|
||||
|
||||
> **Note:** Issues tagged as "🔒Maintainers only" are reserved for project
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> Issues tagged as "🔒Maintainers only" are reserved for project
|
||||
> maintainers. We will not accept pull requests related to these issues.
|
||||
|
||||
@@ -176,8 +176,8 @@ Each server configuration supports the following properties:
|
||||
enabled by default.
|
||||
- **`excludeTools`** (string[]): List of tool names to exclude from this MCP
|
||||
server. Tools listed here will not be available to the model, even if they are
|
||||
exposed by the server. **Note:** `excludeTools` takes precedence over
|
||||
`includeTools` - if a tool is in both lists, it will be excluded.
|
||||
exposed by the server. `excludeTools` takes precedence over `includeTools`. If
|
||||
a tool is in both lists, it will be excluded.
|
||||
- **`targetAudience`** (string): The OAuth Client ID allowlisted on the
|
||||
IAP-protected application you are trying to access. Used with
|
||||
`authProviderType: 'service_account_impersonation'`.
|
||||
@@ -238,7 +238,9 @@ This follows the security principle that if a variable is explicitly configured
|
||||
by the user for a specific server, it constitutes informed consent to share that
|
||||
specific data with that server.
|
||||
|
||||
> **Note:** Even when explicitly defined, you should avoid hardcoding secrets.
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> Even when explicitly defined, you should avoid hardcoding secrets.
|
||||
> Instead, use environment variable expansion (e.g., `"MY_KEY": "$MY_KEY"`) to
|
||||
> securely pull the value from your host environment at runtime.
|
||||
|
||||
@@ -283,10 +285,12 @@ When connecting to an OAuth-enabled server:
|
||||
|
||||
#### Browser redirect requirements
|
||||
|
||||
**Important:** OAuth authentication requires that your local machine can:
|
||||
|
||||
- Open a web browser for authentication
|
||||
- Receive redirects on `http://localhost:7777/oauth/callback`
|
||||
<!-- prettier-ignore -->
|
||||
> [!IMPORTANT]
|
||||
> OAuth authentication requires that your local machine can:
|
||||
>
|
||||
> - Open a web browser for authentication
|
||||
> - Receive redirects on `http://localhost:7777/oauth/callback`
|
||||
|
||||
This feature will not work in:
|
||||
|
||||
@@ -577,7 +581,9 @@ every discovered MCP tool is assigned a strict namespace.
|
||||
[Special syntax for MCP tools](../reference/policy-engine.md#special-syntax-for-mcp-tools)
|
||||
in the Policy Engine documentation.
|
||||
|
||||
> **Warning:** Do not use underscores (`_`) in your MCP server names (e.g., use
|
||||
<!-- prettier-ignore -->
|
||||
> [!WARNING]
|
||||
> Do not use underscores (`_`) in your MCP server names (e.g., use
|
||||
> `my-server` rather than `my_server`). The policy parser splits Fully Qualified
|
||||
> Names (`mcp_server_tool`) on the _first_ underscore following the `mcp_`
|
||||
> prefix. If your server name contains an underscore, the parser will
|
||||
@@ -1116,7 +1122,9 @@ command has no flags.
|
||||
gemini mcp list
|
||||
```
|
||||
|
||||
> **Note on Trust:** For security, `stdio` MCP servers (those using the
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> For security, `stdio` MCP servers (those using the
|
||||
> `command` property) are only tested and displayed as "Connected" if the
|
||||
> current folder is trusted. If the folder is untrusted, they will show as
|
||||
> "Disconnected". Use `gemini trust` to trust the current folder.
|
||||
|
||||
@@ -11,7 +11,9 @@ by the agent when you ask it to "start a plan" using natural language. In this
|
||||
mode, the agent is restricted to read-only tools to allow for safe exploration
|
||||
and planning.
|
||||
|
||||
> **Note:** This tool is not available when the CLI is in YOLO mode.
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> This tool is not available when the CLI is in YOLO mode.
|
||||
|
||||
- **Tool name:** `enter_plan_mode`
|
||||
- **Display name:** Enter Plan Mode
|
||||
|
||||
+4
-4
@@ -57,8 +57,8 @@ implementation, which does not support interactive commands.
|
||||
### Showing color in output
|
||||
|
||||
To show color in the shell output, you need to set the `tools.shell.showColor`
|
||||
setting to `true`. **Note: This setting only applies when
|
||||
`tools.shell.enableInteractiveShell` is enabled.**
|
||||
setting to `true`. This setting only applies when
|
||||
`tools.shell.enableInteractiveShell` is enabled.
|
||||
|
||||
**Example `settings.json`:**
|
||||
|
||||
@@ -75,8 +75,8 @@ setting to `true`. **Note: This setting only applies when
|
||||
### Setting the pager
|
||||
|
||||
You can set a custom pager for the shell output by setting the
|
||||
`tools.shell.pager` setting. The default pager is `cat`. **Note: This setting
|
||||
only applies when `tools.shell.enableInteractiveShell` is enabled.**
|
||||
`tools.shell.pager` setting. The default pager is `cat`. This setting only
|
||||
applies when `tools.shell.enableInteractiveShell` is enabled.
|
||||
|
||||
**Example `settings.json`:**
|
||||
|
||||
|
||||
+6
-1
@@ -319,7 +319,12 @@ export default tseslint.config(
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['./scripts/**/*.js', 'esbuild.config.js', 'packages/core/scripts/**/*.{js,mjs}'],
|
||||
files: [
|
||||
'./scripts/**/*.js',
|
||||
'packages/*/scripts/**/*.js',
|
||||
'esbuild.config.js',
|
||||
'packages/core/scripts/**/*.{js,mjs}',
|
||||
],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
|
||||
Generated
+5
-5
@@ -22,7 +22,7 @@
|
||||
"gemini": "bundle/gemini.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@agentclientprotocol/sdk": "^0.12.0",
|
||||
"@agentclientprotocol/sdk": "^0.16.1",
|
||||
"@octokit/rest": "^22.0.0",
|
||||
"@types/marked": "^5.0.2",
|
||||
"@types/mime-types": "^3.0.1",
|
||||
@@ -84,9 +84,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@agentclientprotocol/sdk": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-0.12.0.tgz",
|
||||
"integrity": "sha512-V8uH/KK1t7utqyJmTA7y7DzKu6+jKFIXM+ZVouz8E55j8Ej2RV42rEvPKn3/PpBJlliI5crcGk1qQhZ7VwaepA==",
|
||||
"version": "0.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-0.16.1.tgz",
|
||||
"integrity": "sha512-1ad+Sc/0sCtZGHthxxvgEUo5Wsbw16I+aF+YwdiLnPwkZG8KAGUEAPK6LM6Pf69lCyJPt1Aomk1d+8oE3C4ZEw==",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
@@ -17531,7 +17531,7 @@
|
||||
"version": "0.36.0-nightly.20260317.2f90b4653",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "^0.12.0",
|
||||
"@agentclientprotocol/sdk": "^0.16.1",
|
||||
"@google/gemini-cli-core": "file:../core",
|
||||
"@google/genai": "1.30.0",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@
|
||||
"LICENSE"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@agentclientprotocol/sdk": "^0.12.0",
|
||||
"@agentclientprotocol/sdk": "^0.16.1",
|
||||
"@octokit/rest": "^22.0.0",
|
||||
"@types/marked": "^5.0.2",
|
||||
"@types/mime-types": "^3.0.1",
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.36.0-nightly.20260317.2f90b4653"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "^0.12.0",
|
||||
"@agentclientprotocol/sdk": "^0.16.1",
|
||||
"@google/gemini-cli-core": "file:../core",
|
||||
"@google/genai": "1.30.0",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
|
||||
@@ -551,7 +551,7 @@ describe('GeminiAgent', () => {
|
||||
});
|
||||
|
||||
expect(session.prompt).toHaveBeenCalled();
|
||||
expect(result).toEqual({ stopReason: 'end_turn' });
|
||||
expect(result).toMatchObject({ stopReason: 'end_turn' });
|
||||
});
|
||||
|
||||
it('should delegate setMode to session', async () => {
|
||||
@@ -750,7 +750,7 @@ describe('Session', () => {
|
||||
content: { type: 'text', text: 'Hello' },
|
||||
},
|
||||
});
|
||||
expect(result).toEqual({ stopReason: 'end_turn' });
|
||||
expect(result).toMatchObject({ stopReason: 'end_turn' });
|
||||
});
|
||||
|
||||
it('should handle /memory command', async () => {
|
||||
@@ -767,7 +767,7 @@ describe('Session', () => {
|
||||
prompt: [{ type: 'text', text: '/memory view' }],
|
||||
});
|
||||
|
||||
expect(result).toEqual({ stopReason: 'end_turn' });
|
||||
expect(result).toMatchObject({ stopReason: 'end_turn' });
|
||||
expect(handleCommandSpy).toHaveBeenCalledWith(
|
||||
'/memory view',
|
||||
expect.any(Object),
|
||||
@@ -789,7 +789,7 @@ describe('Session', () => {
|
||||
prompt: [{ type: 'text', text: '/extensions list' }],
|
||||
});
|
||||
|
||||
expect(result).toEqual({ stopReason: 'end_turn' });
|
||||
expect(result).toMatchObject({ stopReason: 'end_turn' });
|
||||
expect(handleCommandSpy).toHaveBeenCalledWith(
|
||||
'/extensions list',
|
||||
expect.any(Object),
|
||||
@@ -811,7 +811,7 @@ describe('Session', () => {
|
||||
prompt: [{ type: 'text', text: '/extensions explore' }],
|
||||
});
|
||||
|
||||
expect(result).toEqual({ stopReason: 'end_turn' });
|
||||
expect(result).toMatchObject({ stopReason: 'end_turn' });
|
||||
expect(handleCommandSpy).toHaveBeenCalledWith(
|
||||
'/extensions explore',
|
||||
expect.any(Object),
|
||||
@@ -833,7 +833,7 @@ describe('Session', () => {
|
||||
prompt: [{ type: 'text', text: '/restore' }],
|
||||
});
|
||||
|
||||
expect(result).toEqual({ stopReason: 'end_turn' });
|
||||
expect(result).toMatchObject({ stopReason: 'end_turn' });
|
||||
expect(handleCommandSpy).toHaveBeenCalledWith(
|
||||
'/restore',
|
||||
expect.any(Object),
|
||||
@@ -855,7 +855,7 @@ describe('Session', () => {
|
||||
prompt: [{ type: 'text', text: '/init' }],
|
||||
});
|
||||
|
||||
expect(result).toEqual({ stopReason: 'end_turn' });
|
||||
expect(result).toMatchObject({ stopReason: 'end_turn' });
|
||||
expect(handleCommandSpy).toHaveBeenCalledWith('/init', expect.any(Object));
|
||||
expect(mockChat.sendMessageStream).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -909,7 +909,7 @@ describe('Session', () => {
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(result).toEqual({ stopReason: 'end_turn' });
|
||||
expect(result).toMatchObject({ stopReason: 'end_turn' });
|
||||
});
|
||||
|
||||
it('should handle tool call permission request', async () => {
|
||||
|
||||
@@ -699,10 +699,22 @@ export class Session {
|
||||
// It uses `parts` argument but effectively ignores it in current implementation
|
||||
const handled = await this.handleCommand(commandText, parts);
|
||||
if (handled) {
|
||||
return { stopReason: 'end_turn' };
|
||||
return {
|
||||
stopReason: 'end_turn',
|
||||
_meta: {
|
||||
quota: {
|
||||
token_count: { input_tokens: 0, output_tokens: 0 },
|
||||
model_usage: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let totalInputTokens = 0;
|
||||
let totalOutputTokens = 0;
|
||||
const modelUsageMap = new Map<string, { input: number; output: number }>();
|
||||
|
||||
let nextMessage: Content | null = { role: 'user', parts };
|
||||
|
||||
while (nextMessage !== null) {
|
||||
@@ -727,11 +739,25 @@ export class Session {
|
||||
);
|
||||
nextMessage = null;
|
||||
|
||||
let turnInputTokens = 0;
|
||||
let turnOutputTokens = 0;
|
||||
let turnModelId = model;
|
||||
|
||||
for await (const resp of responseStream) {
|
||||
if (pendingSend.signal.aborted) {
|
||||
return { stopReason: CoreToolCallStatus.Cancelled };
|
||||
}
|
||||
|
||||
if (resp.type === StreamEventType.CHUNK && resp.value.usageMetadata) {
|
||||
turnInputTokens =
|
||||
resp.value.usageMetadata.promptTokenCount ?? turnInputTokens;
|
||||
turnOutputTokens =
|
||||
resp.value.usageMetadata.candidatesTokenCount ?? turnOutputTokens;
|
||||
if (resp.value.modelVersion) {
|
||||
turnModelId = resp.value.modelVersion;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
resp.type === StreamEventType.CHUNK &&
|
||||
resp.value.candidates &&
|
||||
@@ -763,6 +789,19 @@ export class Session {
|
||||
}
|
||||
}
|
||||
|
||||
totalInputTokens += turnInputTokens;
|
||||
totalOutputTokens += turnOutputTokens;
|
||||
|
||||
if (turnInputTokens > 0 || turnOutputTokens > 0) {
|
||||
const existing = modelUsageMap.get(turnModelId) ?? {
|
||||
input: 0,
|
||||
output: 0,
|
||||
};
|
||||
existing.input += turnInputTokens;
|
||||
existing.output += turnOutputTokens;
|
||||
modelUsageMap.set(turnModelId, existing);
|
||||
}
|
||||
|
||||
if (pendingSend.signal.aborted) {
|
||||
return { stopReason: CoreToolCallStatus.Cancelled };
|
||||
}
|
||||
@@ -799,7 +838,28 @@ export class Session {
|
||||
}
|
||||
}
|
||||
|
||||
return { stopReason: 'end_turn' };
|
||||
const modelUsageArray = Array.from(modelUsageMap.entries()).map(
|
||||
([modelName, counts]) => ({
|
||||
model: modelName,
|
||||
token_count: {
|
||||
input_tokens: counts.input,
|
||||
output_tokens: counts.output,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
stopReason: 'end_turn',
|
||||
_meta: {
|
||||
quota: {
|
||||
token_count: {
|
||||
input_tokens: totalInputTokens,
|
||||
output_tokens: totalOutputTokens,
|
||||
},
|
||||
model_usage: modelUsageArray,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private async handleCommand(
|
||||
|
||||
@@ -14,7 +14,7 @@ export class AcpFileSystemService implements FileSystemService {
|
||||
constructor(
|
||||
private readonly connection: acp.AgentSideConnection,
|
||||
private readonly sessionId: string,
|
||||
private readonly capabilities: acp.FileSystemCapability,
|
||||
private readonly capabilities: acp.FileSystemCapabilities,
|
||||
private readonly fallback: FileSystemService,
|
||||
) {}
|
||||
|
||||
|
||||
@@ -264,6 +264,7 @@ describe('mcp list command', () => {
|
||||
config: {
|
||||
'allowed-server': { url: 'http://allowed' },
|
||||
},
|
||||
requiredConfig: {},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
Config,
|
||||
resolveToRealPath,
|
||||
applyAdminAllowlist,
|
||||
applyRequiredServers,
|
||||
getAdminBlockedMcpServersMessage,
|
||||
type HookDefinition,
|
||||
type HookEventName,
|
||||
@@ -702,6 +703,19 @@ export async function loadCliConfig(
|
||||
? defaultModel
|
||||
: specifiedModel || defaultModel;
|
||||
const sandboxConfig = await loadSandboxConfig(settings, argv);
|
||||
if (sandboxConfig) {
|
||||
const existingPaths = sandboxConfig.allowedPaths || [];
|
||||
if (settings.tools.sandboxAllowedPaths?.length) {
|
||||
sandboxConfig.allowedPaths = [
|
||||
...new Set([...existingPaths, ...settings.tools.sandboxAllowedPaths]),
|
||||
];
|
||||
}
|
||||
if (settings.tools.sandboxNetworkAccess !== undefined) {
|
||||
sandboxConfig.networkAccess =
|
||||
sandboxConfig.networkAccess || settings.tools.sandboxNetworkAccess;
|
||||
}
|
||||
}
|
||||
|
||||
const screenReader =
|
||||
argv.screenReader !== undefined
|
||||
? argv.screenReader
|
||||
@@ -737,6 +751,25 @@ export async function loadCliConfig(
|
||||
}
|
||||
}
|
||||
|
||||
// Apply admin-required MCP servers (injected regardless of allowlist)
|
||||
if (mcpEnabled) {
|
||||
const requiredMcpConfig = settings.admin?.mcp?.requiredConfig;
|
||||
if (requiredMcpConfig && Object.keys(requiredMcpConfig).length > 0) {
|
||||
const requiredResult = applyRequiredServers(
|
||||
mcpServers ?? {},
|
||||
requiredMcpConfig,
|
||||
);
|
||||
mcpServers = requiredResult.mcpServers;
|
||||
|
||||
if (requiredResult.requiredServerNames.length > 0) {
|
||||
coreEvents.emitConsoleLog(
|
||||
'info',
|
||||
`Admin-required MCP servers injected: ${requiredResult.requiredServerNames.join(', ')}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isAcpMode = !!argv.acp || !!argv.experimentalAcp;
|
||||
let clientName: string | undefined = undefined;
|
||||
if (isAcpMode) {
|
||||
@@ -840,6 +873,7 @@ export async function loadCliConfig(
|
||||
skillsSupport: settings.skills?.enabled ?? true,
|
||||
disabledSkills: settings.skills?.disabled,
|
||||
experimentalJitContext: settings.experimental?.jitContext,
|
||||
experimentalMemoryManager: settings.experimental?.memoryManager,
|
||||
modelSteering: settings.experimental?.modelSteering,
|
||||
topicUpdateNarration: settings.experimental?.topicUpdateNarration,
|
||||
toolOutputMasking: settings.experimental?.toolOutputMasking,
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import * as fs from 'node:fs';
|
||||
import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
import { copyExtension } from './extension-manager.js';
|
||||
|
||||
describe('copyExtension permissions', () => {
|
||||
let tempDir: string;
|
||||
let sourceDir: string;
|
||||
let destDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gemini-permission-test-'));
|
||||
sourceDir = path.join(tempDir, 'source');
|
||||
destDir = path.join(tempDir, 'dest');
|
||||
fs.mkdirSync(sourceDir);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Ensure we can delete the temp directory by making everything writable again
|
||||
const makeWritableSync = (p: string) => {
|
||||
try {
|
||||
const stats = fs.lstatSync(p);
|
||||
fs.chmodSync(p, stats.mode | 0o700);
|
||||
if (stats.isDirectory()) {
|
||||
fs.readdirSync(p).forEach((child) =>
|
||||
makeWritableSync(path.join(p, child)),
|
||||
);
|
||||
}
|
||||
} catch (_e) {
|
||||
// Ignore errors during cleanup
|
||||
}
|
||||
};
|
||||
|
||||
if (fs.existsSync(tempDir)) {
|
||||
makeWritableSync(tempDir);
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('should make destination writable even if source is read-only', async () => {
|
||||
const fileName = 'test.txt';
|
||||
const filePath = path.join(sourceDir, fileName);
|
||||
fs.writeFileSync(filePath, 'hello');
|
||||
|
||||
// Make source read-only: 0o555 for directory, 0o444 for file
|
||||
fs.chmodSync(filePath, 0o444);
|
||||
fs.chmodSync(sourceDir, 0o555);
|
||||
|
||||
// Verify source is read-only
|
||||
expect(() => fs.writeFileSync(filePath, 'fail')).toThrow();
|
||||
|
||||
// Perform copy
|
||||
await copyExtension(sourceDir, destDir);
|
||||
|
||||
// Verify destination is writable
|
||||
const destFilePath = path.join(destDir, fileName);
|
||||
const destFileStats = fs.statSync(destFilePath);
|
||||
const destDirStats = fs.statSync(destDir);
|
||||
|
||||
// Check that owner write bits are set (0o200)
|
||||
expect(destFileStats.mode & 0o200).toBe(0o200);
|
||||
expect(destDirStats.mode & 0o200).toBe(0o200);
|
||||
|
||||
// Verify we can actually write to the destination file
|
||||
fs.writeFileSync(destFilePath, 'writable');
|
||||
expect(fs.readFileSync(destFilePath, 'utf-8')).toBe('writable');
|
||||
|
||||
// Verify we can delete the destination (which requires write bit on destDir)
|
||||
fs.rmSync(destFilePath);
|
||||
expect(fs.existsSync(destFilePath)).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle nested directories with restrictive permissions', async () => {
|
||||
const subDir = path.join(sourceDir, 'subdir');
|
||||
fs.mkdirSync(subDir);
|
||||
const fileName = 'nested.txt';
|
||||
const filePath = path.join(subDir, fileName);
|
||||
fs.writeFileSync(filePath, 'nested content');
|
||||
|
||||
// Make nested structure read-only
|
||||
fs.chmodSync(filePath, 0o444);
|
||||
fs.chmodSync(subDir, 0o555);
|
||||
fs.chmodSync(sourceDir, 0o555);
|
||||
|
||||
// Perform copy
|
||||
await copyExtension(sourceDir, destDir);
|
||||
|
||||
// Verify nested destination is writable
|
||||
const destSubDir = path.join(destDir, 'subdir');
|
||||
const destFilePath = path.join(destSubDir, fileName);
|
||||
|
||||
expect(fs.statSync(destSubDir).mode & 0o200).toBe(0o200);
|
||||
expect(fs.statSync(destFilePath).mode & 0o200).toBe(0o200);
|
||||
|
||||
// Verify we can delete the whole destination tree
|
||||
await fs.promises.rm(destDir, { recursive: true, force: true });
|
||||
expect(fs.existsSync(destDir)).toBe(false);
|
||||
});
|
||||
|
||||
it('should not follow symlinks or modify symlink targets', async () => {
|
||||
const symlinkTarget = path.join(tempDir, 'external-target');
|
||||
fs.writeFileSync(symlinkTarget, 'external content');
|
||||
// Target is read-only
|
||||
fs.chmodSync(symlinkTarget, 0o444);
|
||||
|
||||
const symlinkPath = path.join(sourceDir, 'symlink-file');
|
||||
fs.symlinkSync(symlinkTarget, symlinkPath);
|
||||
|
||||
// Perform copy
|
||||
await copyExtension(sourceDir, destDir);
|
||||
|
||||
const destSymlinkPath = path.join(destDir, 'symlink-file');
|
||||
const destSymlinkStats = fs.lstatSync(destSymlinkPath);
|
||||
|
||||
// Verify it is still a symlink in the destination
|
||||
expect(destSymlinkStats.isSymbolicLink()).toBe(true);
|
||||
|
||||
// Verify the target (external to the extension) was NOT modified
|
||||
const targetStats = fs.statSync(symlinkTarget);
|
||||
// Owner write bit should still NOT be set (0o200)
|
||||
expect(targetStats.mode & 0o200).toBe(0o000);
|
||||
|
||||
// Clean up
|
||||
fs.chmodSync(symlinkTarget, 0o644);
|
||||
});
|
||||
});
|
||||
@@ -15,6 +15,10 @@ import { createExtension } from '../test-utils/createExtension.js';
|
||||
import { EXTENSIONS_DIRECTORY_NAME } from './extensions/variables.js';
|
||||
|
||||
const mockHomedir = vi.hoisted(() => vi.fn(() => '/tmp/mock-home'));
|
||||
const mockIntegrityManager = vi.hoisted(() => ({
|
||||
verify: vi.fn().mockResolvedValue('verified'),
|
||||
store: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
vi.mock('node:os', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('node:os')>();
|
||||
@@ -31,6 +35,9 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
return {
|
||||
...actual,
|
||||
homedir: mockHomedir,
|
||||
ExtensionIntegrityManager: vi
|
||||
.fn()
|
||||
.mockImplementation(() => mockIntegrityManager),
|
||||
loadAgentsFromDirectory: vi
|
||||
.fn()
|
||||
.mockImplementation(async () => ({ agents: [], errors: [] })),
|
||||
@@ -64,6 +71,7 @@ describe('ExtensionManager skills validation', () => {
|
||||
requestConsent: vi.fn().mockResolvedValue(true),
|
||||
requestSetting: vi.fn(),
|
||||
workspaceDir: tempDir,
|
||||
integrityManager: mockIntegrityManager,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -139,6 +147,7 @@ describe('ExtensionManager skills validation', () => {
|
||||
requestConsent: vi.fn().mockResolvedValue(true),
|
||||
requestSetting: vi.fn(),
|
||||
workspaceDir: tempDir,
|
||||
integrityManager: mockIntegrityManager,
|
||||
});
|
||||
|
||||
// 4. Load extensions
|
||||
|
||||
@@ -1248,11 +1248,32 @@ function filterMcpConfig(original: MCPServerConfig): MCPServerConfig {
|
||||
return Object.freeze(rest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively ensures that the owner has write permissions for all files
|
||||
* and directories within the target path.
|
||||
*/
|
||||
async function makeWritableRecursive(targetPath: string): Promise<void> {
|
||||
const stats = await fs.promises.lstat(targetPath);
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
// Ensure directory is rwx for the owner (0o700)
|
||||
await fs.promises.chmod(targetPath, stats.mode | 0o700);
|
||||
const children = await fs.promises.readdir(targetPath);
|
||||
for (const child of children) {
|
||||
await makeWritableRecursive(path.join(targetPath, child));
|
||||
}
|
||||
} else if (stats.isFile()) {
|
||||
// Ensure file is rw for the owner (0o600)
|
||||
await fs.promises.chmod(targetPath, stats.mode | 0o600);
|
||||
}
|
||||
}
|
||||
|
||||
export async function copyExtension(
|
||||
source: string,
|
||||
destination: string,
|
||||
): Promise<void> {
|
||||
await fs.promises.cp(source, destination, { recursive: true });
|
||||
await makeWritableRecursive(destination);
|
||||
}
|
||||
|
||||
function getContextFileNames(config: ExtensionConfig): string[] {
|
||||
|
||||
@@ -36,6 +36,8 @@ vi.mock('node:fs', async (importOriginal) => {
|
||||
rm: vi.fn(),
|
||||
cp: vi.fn(),
|
||||
readFile: vi.fn(),
|
||||
lstat: vi.fn(),
|
||||
chmod: vi.fn(),
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -143,6 +145,11 @@ describe('extensionUpdates', () => {
|
||||
vi.mocked(fs.promises.rm).mockResolvedValue(undefined);
|
||||
vi.mocked(fs.promises.cp).mockResolvedValue(undefined);
|
||||
vi.mocked(fs.promises.readdir).mockResolvedValue([]);
|
||||
vi.mocked(fs.promises.lstat).mockResolvedValue({
|
||||
isDirectory: () => true,
|
||||
mode: 0o755,
|
||||
} as unknown as fs.Stats);
|
||||
vi.mocked(fs.promises.chmod).mockResolvedValue(undefined);
|
||||
vi.mocked(isWorkspaceTrusted).mockReturnValue({
|
||||
isTrusted: true,
|
||||
source: 'file',
|
||||
|
||||
@@ -516,7 +516,9 @@ describe('Policy Engine Integration Tests', () => {
|
||||
);
|
||||
expect(mcpServerRule?.priority).toBe(4.1); // MCP allowed server
|
||||
|
||||
const readOnlyToolRule = rules.find((r) => r.toolName === 'glob');
|
||||
const readOnlyToolRule = rules.find(
|
||||
(r) => r.toolName === 'glob' && !r.subagent,
|
||||
);
|
||||
// Priority 70 in default tier → 1.07 (Overriding Plan Mode Deny)
|
||||
expect(readOnlyToolRule?.priority).toBeCloseTo(1.07, 5);
|
||||
|
||||
@@ -673,7 +675,7 @@ describe('Policy Engine Integration Tests', () => {
|
||||
const server1Rule = rules.find((r) => r.toolName === 'mcp_server1_*');
|
||||
expect(server1Rule?.priority).toBe(4.1); // Allowed servers (user tier)
|
||||
|
||||
const globRule = rules.find((r) => r.toolName === 'glob');
|
||||
const globRule = rules.find((r) => r.toolName === 'glob' && !r.subagent);
|
||||
// Priority 70 in default tier → 1.07
|
||||
expect(globRule?.priority).toBeCloseTo(1.07, 5); // Auto-accept read-only
|
||||
|
||||
|
||||
@@ -338,6 +338,8 @@ describe('loadSandboxConfig', () => {
|
||||
sandbox: {
|
||||
enabled: true,
|
||||
command: 'podman',
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -353,6 +355,8 @@ describe('loadSandboxConfig', () => {
|
||||
sandbox: {
|
||||
enabled: true,
|
||||
image: 'custom/image',
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -367,6 +371,8 @@ describe('loadSandboxConfig', () => {
|
||||
tools: {
|
||||
sandbox: {
|
||||
enabled: false,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -382,6 +388,7 @@ describe('loadSandboxConfig', () => {
|
||||
sandbox: {
|
||||
enabled: true,
|
||||
allowedPaths: ['/settings-path'],
|
||||
networkAccess: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -29,6 +29,7 @@ const VALID_SANDBOX_COMMANDS = [
|
||||
'sandbox-exec',
|
||||
'runsc',
|
||||
'lxc',
|
||||
'windows-native',
|
||||
];
|
||||
|
||||
function isSandboxCommand(
|
||||
@@ -75,8 +76,15 @@ function getSandboxCommand(
|
||||
'gVisor (runsc) sandboxing is only supported on Linux',
|
||||
);
|
||||
}
|
||||
// confirm that specified command exists
|
||||
if (!commandExists.sync(sandbox)) {
|
||||
// windows-native is only supported on Windows
|
||||
if (sandbox === 'windows-native' && os.platform() !== 'win32') {
|
||||
throw new FatalSandboxError(
|
||||
'Windows native sandboxing is only supported on Windows',
|
||||
);
|
||||
}
|
||||
|
||||
// confirm that specified command exists (unless it's built-in)
|
||||
if (sandbox !== 'windows-native' && !commandExists.sync(sandbox)) {
|
||||
throw new FatalSandboxError(
|
||||
`Missing sandbox command '${sandbox}' (from GEMINI_SANDBOX)`,
|
||||
);
|
||||
@@ -149,7 +157,12 @@ export async function loadSandboxConfig(
|
||||
customImage ??
|
||||
packageJson?.config?.sandboxImageUri;
|
||||
|
||||
return command && image
|
||||
const isNative =
|
||||
command === 'windows-native' ||
|
||||
command === 'sandbox-exec' ||
|
||||
command === 'lxc';
|
||||
|
||||
return command && (image || isNative)
|
||||
? { enabled: true, allowedPaths, networkAccess, command, image }
|
||||
: undefined;
|
||||
}
|
||||
|
||||
@@ -2751,6 +2751,28 @@ describe('Settings Loading and Merging', () => {
|
||||
expect(loadedSettings.merged.admin?.mcp?.config).toEqual(mcpServers);
|
||||
});
|
||||
|
||||
it('should map requiredMcpConfig from remote settings', () => {
|
||||
const loadedSettings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
const requiredMcpConfig = {
|
||||
'corp-tool': {
|
||||
url: 'https://mcp.corp/tool',
|
||||
type: 'http' as const,
|
||||
trust: true,
|
||||
},
|
||||
};
|
||||
|
||||
loadedSettings.setRemoteAdminSettings({
|
||||
mcpSetting: {
|
||||
mcpEnabled: true,
|
||||
requiredMcpConfig,
|
||||
},
|
||||
});
|
||||
|
||||
expect(loadedSettings.merged.admin?.mcp?.requiredConfig).toEqual(
|
||||
requiredMcpConfig,
|
||||
);
|
||||
});
|
||||
|
||||
it('should set skills based on unmanagedCapabilitiesEnabled', () => {
|
||||
const loadedSettings = loadSettings();
|
||||
loadedSettings.setRemoteAdminSettings({
|
||||
|
||||
@@ -480,6 +480,7 @@ export class LoadedSettings {
|
||||
admin.mcp = {
|
||||
enabled: mcpSetting?.mcpEnabled,
|
||||
config: mcpSetting?.mcpConfig?.mcpServers,
|
||||
requiredConfig: mcpSetting?.requiredMcpConfig,
|
||||
};
|
||||
admin.extensions = {
|
||||
enabled: cliFeatureSetting?.extensionsSetting?.extensionsEnabled,
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
import {
|
||||
DEFAULT_TRUNCATE_TOOL_OUTPUT_THRESHOLD,
|
||||
DEFAULT_MODEL_CONFIGS,
|
||||
AuthProviderType,
|
||||
type MCPServerConfig,
|
||||
type RequiredMcpServerConfig,
|
||||
type BugCommandSettings,
|
||||
type TelemetrySettings,
|
||||
type AuthType,
|
||||
@@ -1081,6 +1083,20 @@ const SETTINGS_SCHEMA = {
|
||||
ref: 'ModelResolution',
|
||||
},
|
||||
},
|
||||
modelChains: {
|
||||
type: 'object',
|
||||
label: 'Model Chains',
|
||||
category: 'Model',
|
||||
requiresRestart: true,
|
||||
default: DEFAULT_MODEL_CONFIGS.modelChains,
|
||||
description:
|
||||
'Availability policy chains defining fallback behavior for models.',
|
||||
showInDialog: false,
|
||||
additionalProperties: {
|
||||
type: 'array',
|
||||
ref: 'ModelPolicy',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1344,10 +1360,30 @@ const SETTINGS_SCHEMA = {
|
||||
description: oneLine`
|
||||
Legacy full-process sandbox execution environment.
|
||||
Set to a boolean to enable or disable the sandbox, provide a string path to a sandbox profile,
|
||||
or specify an explicit sandbox command (e.g., "docker", "podman", "lxc").
|
||||
or specify an explicit sandbox command (e.g., "docker", "podman", "lxc", "windows-native").
|
||||
`,
|
||||
showInDialog: false,
|
||||
},
|
||||
sandboxAllowedPaths: {
|
||||
type: 'array',
|
||||
label: 'Sandbox Allowed Paths',
|
||||
category: 'Tools',
|
||||
requiresRestart: true,
|
||||
default: [] as string[],
|
||||
description:
|
||||
'List of additional paths that the sandbox is allowed to access.',
|
||||
showInDialog: true,
|
||||
items: { type: 'string' },
|
||||
},
|
||||
sandboxNetworkAccess: {
|
||||
type: 'boolean',
|
||||
label: 'Sandbox Network Access',
|
||||
category: 'Tools',
|
||||
requiresRestart: true,
|
||||
default: false,
|
||||
description: 'Whether the sandbox is allowed to access the network.',
|
||||
showInDialog: true,
|
||||
},
|
||||
shell: {
|
||||
type: 'object',
|
||||
label: 'Shell',
|
||||
@@ -2045,6 +2081,16 @@ const SETTINGS_SCHEMA = {
|
||||
},
|
||||
},
|
||||
},
|
||||
memoryManager: {
|
||||
type: 'boolean',
|
||||
label: 'Memory Manager Agent',
|
||||
category: 'Experimental',
|
||||
requiresRestart: true,
|
||||
default: false,
|
||||
description:
|
||||
'Replace the built-in save_memory tool with a memory manager subagent that supports adding, removing, de-duplicating, and organizing memories.',
|
||||
showInDialog: true,
|
||||
},
|
||||
topicUpdateNarration: {
|
||||
type: 'boolean',
|
||||
label: 'Topic & Update Narration',
|
||||
@@ -2391,7 +2437,7 @@ const SETTINGS_SCHEMA = {
|
||||
category: 'Admin',
|
||||
requiresRestart: false,
|
||||
default: {} as Record<string, MCPServerConfig>,
|
||||
description: 'Admin-configured MCP servers.',
|
||||
description: 'Admin-configured MCP servers (allowlist).',
|
||||
showInDialog: false,
|
||||
mergeStrategy: MergeStrategy.REPLACE,
|
||||
additionalProperties: {
|
||||
@@ -2399,6 +2445,20 @@ const SETTINGS_SCHEMA = {
|
||||
ref: 'MCPServerConfig',
|
||||
},
|
||||
},
|
||||
requiredConfig: {
|
||||
type: 'object',
|
||||
label: 'Required MCP Config',
|
||||
category: 'Admin',
|
||||
requiresRestart: false,
|
||||
default: {} as Record<string, RequiredMcpServerConfig>,
|
||||
description: 'Admin-required MCP servers that are always injected.',
|
||||
showInDialog: false,
|
||||
mergeStrategy: MergeStrategy.REPLACE,
|
||||
additionalProperties: {
|
||||
type: 'object',
|
||||
ref: 'RequiredMcpServerConfig',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
skills: {
|
||||
@@ -2523,11 +2583,72 @@ export const SETTINGS_SCHEMA_DEFINITIONS: Record<
|
||||
type: 'string',
|
||||
description:
|
||||
'Authentication provider used for acquiring credentials (for example `dynamic_discovery`).',
|
||||
enum: [
|
||||
'dynamic_discovery',
|
||||
'google_credentials',
|
||||
'service_account_impersonation',
|
||||
],
|
||||
enum: Object.values(AuthProviderType),
|
||||
},
|
||||
targetAudience: {
|
||||
type: 'string',
|
||||
description:
|
||||
'OAuth target audience (CLIENT_ID.apps.googleusercontent.com).',
|
||||
},
|
||||
targetServiceAccount: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Service account email to impersonate (name@project.iam.gserviceaccount.com).',
|
||||
},
|
||||
},
|
||||
},
|
||||
RequiredMcpServerConfig: {
|
||||
type: 'object',
|
||||
description:
|
||||
'Admin-required MCP server configuration (remote transports only).',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
url: {
|
||||
type: 'string',
|
||||
description: 'URL for the required MCP server.',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
description: 'Transport type for the required server.',
|
||||
enum: ['sse', 'http'],
|
||||
},
|
||||
headers: {
|
||||
type: 'object',
|
||||
description: 'Additional HTTP headers sent to the server.',
|
||||
additionalProperties: { type: 'string' },
|
||||
},
|
||||
timeout: {
|
||||
type: 'number',
|
||||
description: 'Timeout in milliseconds for MCP requests.',
|
||||
},
|
||||
trust: {
|
||||
type: 'boolean',
|
||||
description:
|
||||
'Marks the server as trusted. Defaults to true for admin-required servers.',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
description: 'Human-readable description of the server.',
|
||||
},
|
||||
includeTools: {
|
||||
type: 'array',
|
||||
description: 'Subset of tools enabled for this server.',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
excludeTools: {
|
||||
type: 'array',
|
||||
description: 'Tools disabled for this server.',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
oauth: {
|
||||
type: 'object',
|
||||
description: 'OAuth configuration for authenticating with the server.',
|
||||
additionalProperties: true,
|
||||
},
|
||||
authProviderType: {
|
||||
type: 'string',
|
||||
description: 'Authentication provider used for acquiring credentials.',
|
||||
enum: Object.values(AuthProviderType),
|
||||
},
|
||||
targetAudience: {
|
||||
type: 'string',
|
||||
@@ -2867,6 +2988,34 @@ export const SETTINGS_SCHEMA_DEFINITIONS: Record<
|
||||
},
|
||||
},
|
||||
},
|
||||
ModelPolicy: {
|
||||
type: 'object',
|
||||
description:
|
||||
'Defines the policy for a single model in the availability chain.',
|
||||
properties: {
|
||||
model: { type: 'string' },
|
||||
isLastResort: { type: 'boolean' },
|
||||
actions: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
terminal: { type: 'string', enum: ['silent', 'prompt'] },
|
||||
transient: { type: 'string', enum: ['silent', 'prompt'] },
|
||||
not_found: { type: 'string', enum: ['silent', 'prompt'] },
|
||||
unknown: { type: 'string', enum: ['silent', 'prompt'] },
|
||||
},
|
||||
},
|
||||
stateTransitions: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
terminal: { type: 'string', enum: ['terminal', 'sticky_retry'] },
|
||||
transient: { type: 'string', enum: ['terminal', 'sticky_retry'] },
|
||||
not_found: { type: 'string', enum: ['terminal', 'sticky_retry'] },
|
||||
unknown: { type: 'string', enum: ['terminal', 'sticky_retry'] },
|
||||
},
|
||||
},
|
||||
},
|
||||
required: ['model'],
|
||||
},
|
||||
};
|
||||
|
||||
export function getSettingsSchema(): SettingsSchemaType {
|
||||
|
||||
@@ -1007,10 +1007,18 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
Date.now(),
|
||||
);
|
||||
try {
|
||||
const { memoryContent, fileCount } =
|
||||
await refreshServerHierarchicalMemory(config);
|
||||
let flattenedMemory: string;
|
||||
let fileCount: number;
|
||||
|
||||
const flattenedMemory = flattenMemory(memoryContent);
|
||||
if (config.isJitContextEnabled()) {
|
||||
await config.getContextManager()?.refresh();
|
||||
flattenedMemory = flattenMemory(config.getUserMemory());
|
||||
fileCount = config.getGeminiMdFileCount();
|
||||
} else {
|
||||
const result = await refreshServerHierarchicalMemory(config);
|
||||
flattenedMemory = flattenMemory(result.memoryContent);
|
||||
fileCount = result.fileCount;
|
||||
}
|
||||
|
||||
historyManager.addItem(
|
||||
{
|
||||
|
||||
@@ -116,7 +116,9 @@ describe('policiesCommand', () => {
|
||||
expect(content).toContain(
|
||||
'### Yolo Mode Policies (combined with normal mode policies)',
|
||||
);
|
||||
expect(content).toContain('### Plan Mode Policies');
|
||||
expect(content).toContain(
|
||||
'### Plan Mode Policies (combined with normal mode policies)',
|
||||
);
|
||||
expect(content).toContain(
|
||||
'**DENY** tool: `dangerousTool` [Priority: 10]',
|
||||
);
|
||||
@@ -162,7 +164,9 @@ describe('policiesCommand', () => {
|
||||
const content = (call[0] as { text: string }).text;
|
||||
|
||||
// Plan-only rules appear under Plan Mode section
|
||||
expect(content).toContain('### Plan Mode Policies');
|
||||
expect(content).toContain(
|
||||
'### Plan Mode Policies (combined with normal mode policies)',
|
||||
);
|
||||
// glob ALLOW is plan-only, should appear in plan section
|
||||
expect(content).toContain('**ALLOW** tool: `glob` [Priority: 70]');
|
||||
// shell ALLOW has no modes (applies to all), appears in normal section
|
||||
|
||||
@@ -100,7 +100,10 @@ const listPoliciesCommand: SlashCommand = {
|
||||
'Yolo Mode Policies (combined with normal mode policies)',
|
||||
uniqueYolo,
|
||||
);
|
||||
content += formatSection('Plan Mode Policies', uniquePlan);
|
||||
content += formatSection(
|
||||
'Plan Mode Policies (combined with normal mode policies)',
|
||||
uniquePlan,
|
||||
);
|
||||
|
||||
context.ui.addItem(
|
||||
{
|
||||
|
||||
@@ -68,6 +68,17 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
|
||||
useGemini31 && selectedAuthType === AuthType.USE_GEMINI;
|
||||
|
||||
const manualModelSelected = useMemo(() => {
|
||||
if (
|
||||
config?.getExperimentalDynamicModelConfiguration?.() === true &&
|
||||
config.modelConfigService
|
||||
) {
|
||||
const def = config.modelConfigService.getModelDefinition(preferredModel);
|
||||
// Only treat as manual selection if it's a visible, non-auto model.
|
||||
return def && def.tier !== 'auto' && def.isVisible === true
|
||||
? preferredModel
|
||||
: '';
|
||||
}
|
||||
|
||||
const manualModels = [
|
||||
DEFAULT_GEMINI_MODEL,
|
||||
DEFAULT_GEMINI_FLASH_MODEL,
|
||||
@@ -81,7 +92,7 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
|
||||
return preferredModel;
|
||||
}
|
||||
return '';
|
||||
}, [preferredModel]);
|
||||
}, [preferredModel, config]);
|
||||
|
||||
useKeypress(
|
||||
(key) => {
|
||||
@@ -103,6 +114,47 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
|
||||
);
|
||||
|
||||
const mainOptions = useMemo(() => {
|
||||
// --- DYNAMIC PATH ---
|
||||
if (
|
||||
config?.getExperimentalDynamicModelConfiguration?.() === true &&
|
||||
config.modelConfigService
|
||||
) {
|
||||
const list = Object.entries(
|
||||
config.modelConfigService.getModelDefinitions?.() ?? {},
|
||||
)
|
||||
.filter(([_, m]) => {
|
||||
// Basic visibility and Preview access
|
||||
if (m.isVisible !== true) return false;
|
||||
if (m.isPreview && !shouldShowPreviewModels) return false;
|
||||
// Only auto models are shown on the main menu
|
||||
if (m.tier !== 'auto') return false;
|
||||
return true;
|
||||
})
|
||||
.map(([id, m]) => ({
|
||||
value: id,
|
||||
title: m.displayName ?? getDisplayString(id, config ?? undefined),
|
||||
description:
|
||||
id === 'auto-gemini-3' && useGemini31
|
||||
? (m.dialogDescription ?? '').replace(
|
||||
'gemini-3-pro',
|
||||
'gemini-3.1-pro',
|
||||
)
|
||||
: (m.dialogDescription ?? ''),
|
||||
key: id,
|
||||
}));
|
||||
|
||||
list.push({
|
||||
value: 'Manual',
|
||||
title: manualModelSelected
|
||||
? `Manual (${getDisplayString(manualModelSelected, config ?? undefined)})`
|
||||
: 'Manual',
|
||||
description: 'Manually select a model',
|
||||
key: 'Manual',
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
// --- LEGACY PATH ---
|
||||
const list = [
|
||||
{
|
||||
value: DEFAULT_GEMINI_MODEL_AUTO,
|
||||
@@ -132,10 +184,65 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
|
||||
});
|
||||
}
|
||||
return list;
|
||||
}, [shouldShowPreviewModels, manualModelSelected, useGemini31]);
|
||||
}, [config, shouldShowPreviewModels, manualModelSelected, useGemini31]);
|
||||
|
||||
const manualOptions = useMemo(() => {
|
||||
const isFreeTier = config?.getUserTier() === UserTierId.FREE;
|
||||
// --- DYNAMIC PATH ---
|
||||
if (
|
||||
config?.getExperimentalDynamicModelConfiguration?.() === true &&
|
||||
config.modelConfigService
|
||||
) {
|
||||
const list = Object.entries(
|
||||
config.modelConfigService.getModelDefinitions?.() ?? {},
|
||||
)
|
||||
.filter(([id, m]) => {
|
||||
// Basic visibility and Preview access
|
||||
if (m.isVisible !== true) return false;
|
||||
if (m.isPreview && !shouldShowPreviewModels) return false;
|
||||
// Auto models are for main menu only
|
||||
if (m.tier === 'auto') return false;
|
||||
// Pro models are shown for users with pro access
|
||||
if (!hasAccessToProModel && m.tier === 'pro') return false;
|
||||
// 3.1 Preview Flash-lite is only available on free tier
|
||||
if (m.tier === 'flash-lite' && m.isPreview && !isFreeTier)
|
||||
return false;
|
||||
|
||||
// Flag Guard: Versioned models only show if their flag is active.
|
||||
if (id === PREVIEW_GEMINI_3_1_MODEL && !useGemini31) return false;
|
||||
if (id === PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL && !useGemini31)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
})
|
||||
.map(([id, m]) => {
|
||||
const resolvedId = config.modelConfigService.resolveModelId(id, {
|
||||
useGemini3_1: useGemini31,
|
||||
useCustomTools: useCustomToolModel,
|
||||
});
|
||||
// Title ID is the resolved ID without custom tools flag
|
||||
const titleId = config.modelConfigService.resolveModelId(id, {
|
||||
useGemini3_1: useGemini31,
|
||||
});
|
||||
return {
|
||||
value: resolvedId,
|
||||
title:
|
||||
m.displayName ?? getDisplayString(titleId, config ?? undefined),
|
||||
key: id,
|
||||
};
|
||||
});
|
||||
|
||||
// Deduplicate: only show one entry per unique resolved model value.
|
||||
// This is needed because 3 pro and 3.1 pro models can resolve to the same value.
|
||||
const seen = new Set<string>();
|
||||
return list.filter((option) => {
|
||||
if (seen.has(option.value)) return false;
|
||||
seen.add(option.value);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// --- LEGACY PATH ---
|
||||
const list = [
|
||||
{
|
||||
value: DEFAULT_GEMINI_MODEL,
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/* eslint-env node */
|
||||
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
/**
|
||||
* Compiles the GeminiSandbox C# helper on Windows.
|
||||
* This is used to provide native restricted token sandboxing.
|
||||
*/
|
||||
function compileWindowsSandbox() {
|
||||
if (os.platform() !== 'win32') {
|
||||
return;
|
||||
}
|
||||
|
||||
const srcHelperPath = path.resolve(
|
||||
__dirname,
|
||||
'../src/services/scripts/GeminiSandbox.exe',
|
||||
);
|
||||
const distHelperPath = path.resolve(
|
||||
__dirname,
|
||||
'../dist/src/services/scripts/GeminiSandbox.exe',
|
||||
);
|
||||
const sourcePath = path.resolve(
|
||||
__dirname,
|
||||
'../src/services/scripts/GeminiSandbox.cs',
|
||||
);
|
||||
|
||||
if (!fs.existsSync(sourcePath)) {
|
||||
console.error(`Sandbox source not found at ${sourcePath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure directories exist
|
||||
[srcHelperPath, distHelperPath].forEach((p) => {
|
||||
const dir = path.dirname(p);
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
// Find csc.exe (C# Compiler) which is built into Windows .NET Framework
|
||||
const systemRoot = process.env['SystemRoot'] || 'C:\\Windows';
|
||||
const cscPaths = [
|
||||
'csc.exe', // Try in PATH first
|
||||
path.join(
|
||||
systemRoot,
|
||||
'Microsoft.NET',
|
||||
'Framework64',
|
||||
'v4.0.30319',
|
||||
'csc.exe',
|
||||
),
|
||||
path.join(
|
||||
systemRoot,
|
||||
'Microsoft.NET',
|
||||
'Framework',
|
||||
'v4.0.30319',
|
||||
'csc.exe',
|
||||
),
|
||||
];
|
||||
|
||||
let csc = undefined;
|
||||
for (const p of cscPaths) {
|
||||
if (p === 'csc.exe') {
|
||||
const result = spawnSync('where', ['csc.exe'], { stdio: 'ignore' });
|
||||
if (result.status === 0) {
|
||||
csc = 'csc.exe';
|
||||
break;
|
||||
}
|
||||
} else if (fs.existsSync(p)) {
|
||||
csc = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!csc) {
|
||||
console.warn(
|
||||
'Windows C# compiler (csc.exe) not found. Native sandboxing will attempt to compile on first run.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Compiling native Windows sandbox helper...`);
|
||||
// Compile to src
|
||||
let result = spawnSync(
|
||||
csc,
|
||||
[`/out:${srcHelperPath}`, '/optimize', sourcePath],
|
||||
{
|
||||
stdio: 'inherit',
|
||||
},
|
||||
);
|
||||
|
||||
if (result.status === 0) {
|
||||
console.log('Successfully compiled GeminiSandbox.exe to src');
|
||||
// Copy to dist if dist exists
|
||||
const distDir = path.resolve(__dirname, '../dist');
|
||||
if (fs.existsSync(distDir)) {
|
||||
const distScriptsDir = path.dirname(distHelperPath);
|
||||
if (!fs.existsSync(distScriptsDir)) {
|
||||
fs.mkdirSync(distScriptsDir, { recursive: true });
|
||||
}
|
||||
fs.copyFileSync(srcHelperPath, distHelperPath);
|
||||
console.log('Successfully copied GeminiSandbox.exe to dist');
|
||||
}
|
||||
} else {
|
||||
console.error('Failed to compile Windows sandbox helper.');
|
||||
}
|
||||
}
|
||||
|
||||
compileWindowsSandbox();
|
||||
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { MemoryManagerAgent } from './memory-manager-agent.js';
|
||||
import {
|
||||
ASK_USER_TOOL_NAME,
|
||||
EDIT_TOOL_NAME,
|
||||
GLOB_TOOL_NAME,
|
||||
GREP_TOOL_NAME,
|
||||
LS_TOOL_NAME,
|
||||
READ_FILE_TOOL_NAME,
|
||||
WRITE_FILE_TOOL_NAME,
|
||||
} from '../tools/tool-names.js';
|
||||
import { Storage } from '../config/storage.js';
|
||||
import type { Config } from '../config/config.js';
|
||||
import type { HierarchicalMemory } from '../config/memory.js';
|
||||
|
||||
function createMockConfig(memory: string | HierarchicalMemory = ''): Config {
|
||||
return {
|
||||
getUserMemory: vi.fn().mockReturnValue(memory),
|
||||
} as unknown as Config;
|
||||
}
|
||||
|
||||
describe('MemoryManagerAgent', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should have the correct name "save_memory"', () => {
|
||||
const agent = MemoryManagerAgent(createMockConfig());
|
||||
expect(agent.name).toBe('save_memory');
|
||||
});
|
||||
|
||||
it('should be a local agent', () => {
|
||||
const agent = MemoryManagerAgent(createMockConfig());
|
||||
expect(agent.kind).toBe('local');
|
||||
});
|
||||
|
||||
it('should have a description', () => {
|
||||
const agent = MemoryManagerAgent(createMockConfig());
|
||||
expect(agent.description).toBeTruthy();
|
||||
expect(agent.description).toContain('memory');
|
||||
});
|
||||
|
||||
it('should have a system prompt with memory management instructions', () => {
|
||||
const agent = MemoryManagerAgent(createMockConfig());
|
||||
const prompt = agent.promptConfig.systemPrompt;
|
||||
const globalGeminiDir = Storage.getGlobalGeminiDir();
|
||||
expect(prompt).toContain(`Global (${globalGeminiDir}`);
|
||||
expect(prompt).toContain('Project (./');
|
||||
expect(prompt).toContain('Memory Hierarchy');
|
||||
expect(prompt).toContain('De-duplicating');
|
||||
expect(prompt).toContain('Adding');
|
||||
expect(prompt).toContain('Removing stale entries');
|
||||
expect(prompt).toContain('Organizing');
|
||||
expect(prompt).toContain('Routing');
|
||||
});
|
||||
|
||||
it('should have efficiency guidelines in the system prompt', () => {
|
||||
const agent = MemoryManagerAgent(createMockConfig());
|
||||
const prompt = agent.promptConfig.systemPrompt;
|
||||
expect(prompt).toContain('Efficiency & Performance');
|
||||
expect(prompt).toContain('Use as few turns as possible');
|
||||
expect(prompt).toContain('Do not perform any exploration');
|
||||
expect(prompt).toContain('Be strategic with your thinking');
|
||||
expect(prompt).toContain('Context Awareness');
|
||||
});
|
||||
|
||||
it('should inject hierarchical memory into initial context', () => {
|
||||
const config = createMockConfig({
|
||||
global:
|
||||
'--- Context from: ../../.gemini/GEMINI.md ---\nglobal context\n--- End of Context from: ../../.gemini/GEMINI.md ---',
|
||||
project:
|
||||
'--- Context from: .gemini/GEMINI.md ---\nproject context\n--- End of Context from: .gemini/GEMINI.md ---',
|
||||
});
|
||||
|
||||
const agent = MemoryManagerAgent(config);
|
||||
const query = agent.promptConfig.query;
|
||||
|
||||
expect(query).toContain('# Initial Context');
|
||||
expect(query).toContain('global context');
|
||||
expect(query).toContain('project context');
|
||||
});
|
||||
|
||||
it('should inject flat string memory into initial context', () => {
|
||||
const config = createMockConfig('flat memory content');
|
||||
|
||||
const agent = MemoryManagerAgent(config);
|
||||
const query = agent.promptConfig.query;
|
||||
|
||||
expect(query).toContain('# Initial Context');
|
||||
expect(query).toContain('flat memory content');
|
||||
});
|
||||
|
||||
it('should exclude extension memory from initial context', () => {
|
||||
const config = createMockConfig({
|
||||
global: 'global context',
|
||||
extension: 'extension context that should be excluded',
|
||||
project: 'project context',
|
||||
});
|
||||
|
||||
const agent = MemoryManagerAgent(config);
|
||||
const query = agent.promptConfig.query;
|
||||
|
||||
expect(query).toContain('global context');
|
||||
expect(query).toContain('project context');
|
||||
expect(query).not.toContain('extension context');
|
||||
});
|
||||
|
||||
it('should not include initial context when memory is empty', () => {
|
||||
const agent = MemoryManagerAgent(createMockConfig());
|
||||
const query = agent.promptConfig.query;
|
||||
|
||||
expect(query).not.toContain('# Initial Context');
|
||||
});
|
||||
|
||||
it('should have file-management and search tools', () => {
|
||||
const agent = MemoryManagerAgent(createMockConfig());
|
||||
expect(agent.toolConfig).toBeDefined();
|
||||
expect(agent.toolConfig!.tools).toEqual(
|
||||
expect.arrayContaining([
|
||||
READ_FILE_TOOL_NAME,
|
||||
EDIT_TOOL_NAME,
|
||||
WRITE_FILE_TOOL_NAME,
|
||||
LS_TOOL_NAME,
|
||||
GLOB_TOOL_NAME,
|
||||
GREP_TOOL_NAME,
|
||||
ASK_USER_TOOL_NAME,
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should require a "request" input parameter', () => {
|
||||
const agent = MemoryManagerAgent(createMockConfig());
|
||||
const schema = agent.inputConfig.inputSchema as Record<string, unknown>;
|
||||
expect(schema).toBeDefined();
|
||||
expect(schema['properties']).toHaveProperty('request');
|
||||
expect(schema['required']).toContain('request');
|
||||
});
|
||||
|
||||
it('should use a fast model', () => {
|
||||
const agent = MemoryManagerAgent(createMockConfig());
|
||||
expect(agent.modelConfig.model).toBe('flash');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import type { LocalAgentDefinition } from './types.js';
|
||||
import {
|
||||
ASK_USER_TOOL_NAME,
|
||||
EDIT_TOOL_NAME,
|
||||
GLOB_TOOL_NAME,
|
||||
GREP_TOOL_NAME,
|
||||
LS_TOOL_NAME,
|
||||
READ_FILE_TOOL_NAME,
|
||||
WRITE_FILE_TOOL_NAME,
|
||||
} from '../tools/tool-names.js';
|
||||
import { Storage } from '../config/storage.js';
|
||||
import { flattenMemory } from '../config/memory.js';
|
||||
import { GEMINI_MODEL_ALIAS_FLASH } from '../config/models.js';
|
||||
import type { Config } from '../config/config.js';
|
||||
|
||||
const MemoryManagerSchema = z.object({
|
||||
response: z
|
||||
.string()
|
||||
.describe('A summary of the memory operations performed.'),
|
||||
});
|
||||
|
||||
/**
|
||||
* A memory management agent that replaces the built-in save_memory tool.
|
||||
* It provides richer memory operations: adding, removing, de-duplicating,
|
||||
* and organizing memories in the global GEMINI.md file.
|
||||
*
|
||||
* Users can override this agent by placing a custom save_memory.md
|
||||
* in ~/.gemini/agents/ or .gemini/agents/.
|
||||
*/
|
||||
export const MemoryManagerAgent = (
|
||||
config: Config,
|
||||
): LocalAgentDefinition<typeof MemoryManagerSchema> => {
|
||||
const globalGeminiDir = Storage.getGlobalGeminiDir();
|
||||
|
||||
const getInitialContext = (): string => {
|
||||
const memory = config.getUserMemory();
|
||||
// Only include global and project memory — extension memory is read-only
|
||||
// and not relevant to the memory manager.
|
||||
const content =
|
||||
typeof memory === 'string'
|
||||
? memory
|
||||
: flattenMemory({ global: memory.global, project: memory.project });
|
||||
if (!content.trim()) return '';
|
||||
return `\n# Initial Context\n\n${content}\n`;
|
||||
};
|
||||
|
||||
const buildSystemPrompt = (): string =>
|
||||
`
|
||||
You are a memory management agent maintaining user memories in GEMINI.md files.
|
||||
|
||||
# Memory Hierarchy
|
||||
|
||||
## Global (${globalGeminiDir})
|
||||
- \`${globalGeminiDir}/GEMINI.md\` — Cross-project user preferences, key personal info,
|
||||
and habits that apply everywhere.
|
||||
|
||||
## Project (./)
|
||||
- \`./GEMINI.md\` — **Table of Contents** for project-specific context:
|
||||
architecture decisions, conventions, key contacts, and references to
|
||||
subdirectory GEMINI.md files for detailed context.
|
||||
- Subdirectory GEMINI.md files (e.g. \`src/GEMINI.md\`, \`docs/GEMINI.md\`) —
|
||||
detailed, domain-specific context for that part of the project. Reference
|
||||
these from the root \`./GEMINI.md\`.
|
||||
|
||||
## Routing
|
||||
|
||||
When adding a memory, route it to the right store:
|
||||
- **Global**: User preferences, personal info, tool aliases, cross-project habits → **global**
|
||||
- **Project Root**: Project architecture, conventions, workflows, team info → **project root**
|
||||
- **Subdirectory**: Detailed context about a specific module or directory → **subdirectory
|
||||
GEMINI.md**, with a reference added to the project root
|
||||
|
||||
- **Ambiguity**: If a memory (like a coding preference or workflow) could be interpreted as either a global habit or a project-specific convention, you **MUST** use \`${ASK_USER_TOOL_NAME}\` to clarify the user's intent. Do NOT make a unilateral decision when ambiguity exists between Global and Project stores.
|
||||
|
||||
# Operations
|
||||
|
||||
1. **Adding** — Route to the correct store and file. Check for duplicates in your provided context first.
|
||||
2. **Removing stale entries** — Delete outdated or unwanted entries. Clean up
|
||||
dangling references.
|
||||
3. **De-duplicating** — Semantically equivalent entries should be combined. Keep the most informative version.
|
||||
4. **Organizing** — Restructure for clarity. Update references between files.
|
||||
|
||||
# Restrictions
|
||||
- Keep GEMINI.md files lean — they are loaded into context every session.
|
||||
- Keep entries concise.
|
||||
- Edit surgically — preserve existing structure and user-authored content.
|
||||
- NEVER write or read any files other than GEMINI.md files.
|
||||
|
||||
# Efficiency & Performance
|
||||
- **Use as few turns as possible.** Execute independent reads and writes to different files in parallel by calling multiple tools in a single turn.
|
||||
- **Do not perform any exploration of the codebase.** Try to use the provided file context and only search additional GEMINI.md files as needed to accomplish your task.
|
||||
- **Be strategic with your thinking.** carefully decide where to route memories and how to de-duplicate memories, but be decisive with simple memory writes.
|
||||
- **Minimize file system operations.** You should typically only modify the GEMINI.md files that are already provided in your context. Only read or write to other files if explicitly directed or if you are following a specific reference from an existing memory file.
|
||||
- **Context Awareness.** If a file's content is already provided in the "Initial Context" section, you do not need to call \`read_file\` for it.
|
||||
|
||||
# Insufficient context
|
||||
If you find that you have insufficient context to read or modify the memories as described,
|
||||
reply with what you need, and exit. Do not search the codebase for the missing context.
|
||||
`.trim();
|
||||
|
||||
return {
|
||||
kind: 'local',
|
||||
name: 'save_memory',
|
||||
displayName: 'Memory Manager',
|
||||
description: `Writes and reads memory, preferences or facts across ALL future sessions. Use this for recurring instructions like coding styles or tool aliases.`,
|
||||
inputConfig: {
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
request: {
|
||||
type: 'string',
|
||||
description:
|
||||
'The memory operation to perform. Examples: "Remember that I prefer tabs over spaces", "Clean up stale memories", "De-duplicate my memories", "Organize my memories".',
|
||||
},
|
||||
},
|
||||
required: ['request'],
|
||||
},
|
||||
},
|
||||
outputConfig: {
|
||||
outputName: 'result',
|
||||
description: 'A summary of the memory operations performed.',
|
||||
schema: MemoryManagerSchema,
|
||||
},
|
||||
modelConfig: {
|
||||
model: GEMINI_MODEL_ALIAS_FLASH,
|
||||
},
|
||||
toolConfig: {
|
||||
tools: [
|
||||
READ_FILE_TOOL_NAME,
|
||||
EDIT_TOOL_NAME,
|
||||
WRITE_FILE_TOOL_NAME,
|
||||
LS_TOOL_NAME,
|
||||
GLOB_TOOL_NAME,
|
||||
GREP_TOOL_NAME,
|
||||
ASK_USER_TOOL_NAME,
|
||||
],
|
||||
},
|
||||
get promptConfig() {
|
||||
return {
|
||||
systemPrompt: buildSystemPrompt(),
|
||||
query: `${getInitialContext()}\${request}`,
|
||||
};
|
||||
},
|
||||
runConfig: {
|
||||
maxTimeMinutes: 5,
|
||||
maxTurns: 10,
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -13,6 +13,7 @@ import { CodebaseInvestigatorAgent } from './codebase-investigator.js';
|
||||
import { CliHelpAgent } from './cli-help-agent.js';
|
||||
import { GeneralistAgent } from './generalist-agent.js';
|
||||
import { BrowserAgentDefinition } from './browser/browserAgentDefinition.js';
|
||||
import { MemoryManagerAgent } from './memory-manager-agent.js';
|
||||
import { A2AAuthProviderFactory } from './auth-provider/factory.js';
|
||||
import type { AuthenticationHandler } from '@a2a-js/sdk/client';
|
||||
import { type z } from 'zod';
|
||||
@@ -249,6 +250,24 @@ export class AgentRegistry {
|
||||
if (browserConfig.enabled) {
|
||||
this.registerLocalAgent(BrowserAgentDefinition(this.config));
|
||||
}
|
||||
|
||||
// Register the memory manager agent as a replacement for the save_memory tool.
|
||||
if (this.config.isMemoryManagerEnabled()) {
|
||||
this.registerLocalAgent(MemoryManagerAgent(this.config));
|
||||
|
||||
// Ensure the global .gemini directory is accessible to tools.
|
||||
// This allows the save_memory agent to read and write to it.
|
||||
// Access control is enforced by the Policy Engine (memory-manager.toml).
|
||||
try {
|
||||
const globalDir = Storage.getGlobalGeminiDir();
|
||||
this.config.getWorkspaceContext().addDirectory(globalDir);
|
||||
} catch (e) {
|
||||
debugLogger.warn(
|
||||
`[AgentRegistry] Could not add global .gemini directory to workspace:`,
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async refreshAgents(): Promise<void> {
|
||||
|
||||
@@ -19,6 +19,8 @@ import {
|
||||
PREVIEW_GEMINI_3_1_MODEL,
|
||||
} from '../config/models.js';
|
||||
import { AuthType } from '../core/contentGenerator.js';
|
||||
import { ModelConfigService } from '../services/modelConfigService.js';
|
||||
import { DEFAULT_MODEL_CONFIGS } from '../config/defaultModelConfigs.js';
|
||||
|
||||
const createMockConfig = (overrides: Partial<Config> = {}): Config => {
|
||||
const config = {
|
||||
@@ -163,6 +165,66 @@ describe('policyHelpers', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolvePolicyChain behavior is identical between dynamic and legacy implementations', () => {
|
||||
const testCases = [
|
||||
{ name: 'Default Auto', model: DEFAULT_GEMINI_MODEL_AUTO },
|
||||
{ name: 'Gemini 3 Auto', model: 'auto-gemini-3' },
|
||||
{ name: 'Flash Lite', model: DEFAULT_GEMINI_FLASH_LITE_MODEL },
|
||||
{
|
||||
name: 'Gemini 3 Auto (3.1 Enabled)',
|
||||
model: 'auto-gemini-3',
|
||||
useGemini31: true,
|
||||
},
|
||||
{
|
||||
name: 'Gemini 3 Auto (3.1 + Custom Tools)',
|
||||
model: 'auto-gemini-3',
|
||||
useGemini31: true,
|
||||
authType: AuthType.USE_GEMINI,
|
||||
},
|
||||
{
|
||||
name: 'Gemini 3 Auto (No Access)',
|
||||
model: 'auto-gemini-3',
|
||||
hasAccess: false,
|
||||
},
|
||||
{ name: 'Concrete Model (2.5 Pro)', model: 'gemini-2.5-pro' },
|
||||
{ name: 'Custom Model', model: 'my-custom-model' },
|
||||
{
|
||||
name: 'Wrap Around',
|
||||
model: DEFAULT_GEMINI_MODEL_AUTO,
|
||||
wrapsAround: true,
|
||||
},
|
||||
];
|
||||
|
||||
testCases.forEach(
|
||||
({ name, model, useGemini31, hasAccess, authType, wrapsAround }) => {
|
||||
it(`achieves parity for: ${name}`, () => {
|
||||
const createBaseConfig = (dynamic: boolean) =>
|
||||
createMockConfig({
|
||||
getExperimentalDynamicModelConfiguration: () => dynamic,
|
||||
getModel: () => model,
|
||||
getGemini31LaunchedSync: () => useGemini31 ?? false,
|
||||
getHasAccessToPreviewModel: () => hasAccess ?? true,
|
||||
getContentGeneratorConfig: () => ({ authType }),
|
||||
modelConfigService: new ModelConfigService(DEFAULT_MODEL_CONFIGS),
|
||||
});
|
||||
|
||||
const legacyChain = resolvePolicyChain(
|
||||
createBaseConfig(false),
|
||||
model,
|
||||
wrapsAround,
|
||||
);
|
||||
const dynamicChain = resolvePolicyChain(
|
||||
createBaseConfig(true),
|
||||
model,
|
||||
wrapsAround,
|
||||
);
|
||||
|
||||
expect(dynamicChain).toEqual(legacyChain);
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('buildFallbackPolicyContext', () => {
|
||||
it('returns remaining candidates after the failed model', () => {
|
||||
const chain = [
|
||||
|
||||
@@ -53,12 +53,57 @@ export function resolvePolicyChain(
|
||||
useGemini31,
|
||||
useCustomToolModel,
|
||||
hasAccessToPreview,
|
||||
config,
|
||||
);
|
||||
const isAutoPreferred = preferredModel
|
||||
? isAutoModel(preferredModel, config)
|
||||
: false;
|
||||
const isAutoConfigured = isAutoModel(configuredModel, config);
|
||||
|
||||
// --- DYNAMIC PATH ---
|
||||
if (config.getExperimentalDynamicModelConfiguration?.() === true) {
|
||||
const context = {
|
||||
useGemini3_1: useGemini31,
|
||||
useCustomTools: useCustomToolModel,
|
||||
};
|
||||
|
||||
if (resolvedModel === DEFAULT_GEMINI_FLASH_LITE_MODEL) {
|
||||
chain = config.modelConfigService.resolveChain('lite', context);
|
||||
} else if (
|
||||
isGemini3Model(resolvedModel, config) ||
|
||||
isAutoModel(preferredModel ?? '', config) ||
|
||||
isAutoModel(configuredModel, config)
|
||||
) {
|
||||
// 1. Try to find a chain specifically for the current configured alias
|
||||
if (
|
||||
isAutoModel(configuredModel, config) &&
|
||||
config.modelConfigService.getModelChain(configuredModel)
|
||||
) {
|
||||
chain = config.modelConfigService.resolveChain(
|
||||
configuredModel,
|
||||
context,
|
||||
);
|
||||
}
|
||||
// 2. Fallback to family-based auto-routing
|
||||
if (!chain) {
|
||||
const previewEnabled =
|
||||
hasAccessToPreview &&
|
||||
(isGemini3Model(resolvedModel, config) ||
|
||||
preferredModel === PREVIEW_GEMINI_MODEL_AUTO ||
|
||||
configuredModel === PREVIEW_GEMINI_MODEL_AUTO);
|
||||
const chainKey = previewEnabled ? 'preview' : 'default';
|
||||
chain = config.modelConfigService.resolveChain(chainKey, context);
|
||||
}
|
||||
}
|
||||
if (!chain) {
|
||||
// No matching modelChains found, default to single model chain
|
||||
chain = createSingleModelChain(modelFromConfig);
|
||||
}
|
||||
return applyDynamicSlicing(chain, resolvedModel, wrapsAround);
|
||||
}
|
||||
|
||||
// --- LEGACY PATH ---
|
||||
|
||||
if (resolvedModel === DEFAULT_GEMINI_FLASH_LITE_MODEL) {
|
||||
chain = getFlashLitePolicyChain();
|
||||
} else if (
|
||||
@@ -90,7 +135,17 @@ export function resolvePolicyChain(
|
||||
} else {
|
||||
chain = createSingleModelChain(modelFromConfig);
|
||||
}
|
||||
return applyDynamicSlicing(chain, resolvedModel, wrapsAround);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies active-index slicing and wrap-around logic to a chain template.
|
||||
*/
|
||||
function applyDynamicSlicing(
|
||||
chain: ModelPolicy[],
|
||||
resolvedModel: string,
|
||||
wrapsAround: boolean,
|
||||
): ModelPolicyChain {
|
||||
const activeIndex = chain.findIndex(
|
||||
(policy) => policy.model === resolvedModel,
|
||||
);
|
||||
|
||||
@@ -224,6 +224,89 @@ describe('Admin Controls', () => {
|
||||
const result = sanitizeAdminSettings(input);
|
||||
expect(result.strictModeDisabled).toBe(true);
|
||||
});
|
||||
|
||||
it('should parse requiredMcpServers from mcpConfigJson', () => {
|
||||
const mcpConfig = {
|
||||
mcpServers: {
|
||||
'allowed-server': {
|
||||
url: 'http://allowed.com',
|
||||
type: 'sse' as const,
|
||||
},
|
||||
},
|
||||
requiredMcpServers: {
|
||||
'corp-tool': {
|
||||
url: 'https://mcp.corp/tool',
|
||||
type: 'http' as const,
|
||||
trust: true,
|
||||
description: 'Corp compliance tool',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const input: FetchAdminControlsResponse = {
|
||||
mcpSetting: {
|
||||
mcpEnabled: true,
|
||||
mcpConfigJson: JSON.stringify(mcpConfig),
|
||||
},
|
||||
};
|
||||
|
||||
const result = sanitizeAdminSettings(input);
|
||||
expect(result.mcpSetting?.mcpConfig?.mcpServers).toEqual(
|
||||
mcpConfig.mcpServers,
|
||||
);
|
||||
expect(result.mcpSetting?.requiredMcpConfig).toEqual(
|
||||
mcpConfig.requiredMcpServers,
|
||||
);
|
||||
});
|
||||
|
||||
it('should sort requiredMcpServers tool lists for stable comparison', () => {
|
||||
const mcpConfig = {
|
||||
requiredMcpServers: {
|
||||
'corp-tool': {
|
||||
url: 'https://mcp.corp/tool',
|
||||
type: 'http' as const,
|
||||
includeTools: ['toolC', 'toolA', 'toolB'],
|
||||
excludeTools: ['toolZ', 'toolX'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const input: FetchAdminControlsResponse = {
|
||||
mcpSetting: {
|
||||
mcpEnabled: true,
|
||||
mcpConfigJson: JSON.stringify(mcpConfig),
|
||||
},
|
||||
};
|
||||
|
||||
const result = sanitizeAdminSettings(input);
|
||||
const corpTool = result.mcpSetting?.requiredMcpConfig?.['corp-tool'];
|
||||
expect(corpTool?.includeTools).toEqual(['toolA', 'toolB', 'toolC']);
|
||||
expect(corpTool?.excludeTools).toEqual(['toolX', 'toolZ']);
|
||||
});
|
||||
|
||||
it('should handle mcpConfigJson with only requiredMcpServers and no mcpServers', () => {
|
||||
const mcpConfig = {
|
||||
requiredMcpServers: {
|
||||
'required-only': {
|
||||
url: 'https://required.corp/tool',
|
||||
type: 'http' as const,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const input: FetchAdminControlsResponse = {
|
||||
mcpSetting: {
|
||||
mcpEnabled: true,
|
||||
mcpConfigJson: JSON.stringify(mcpConfig),
|
||||
},
|
||||
};
|
||||
|
||||
const result = sanitizeAdminSettings(input);
|
||||
expect(result.mcpSetting?.mcpConfig?.mcpServers).toBeUndefined();
|
||||
expect(result.mcpSetting?.requiredMcpConfig).toEqual(
|
||||
mcpConfig.requiredMcpServers,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isDeepStrictEqual verification', () => {
|
||||
|
||||
@@ -48,6 +48,16 @@ export function sanitizeAdminSettings(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mcpConfig.requiredMcpServers) {
|
||||
for (const server of Object.values(mcpConfig.requiredMcpServers)) {
|
||||
if (server.includeTools) {
|
||||
server.includeTools.sort();
|
||||
}
|
||||
if (server.excludeTools) {
|
||||
server.excludeTools.sort();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_e) {
|
||||
// Ignore parsing errors
|
||||
@@ -77,6 +87,7 @@ export function sanitizeAdminSettings(
|
||||
mcpSetting: {
|
||||
mcpEnabled: sanitized.mcpSetting?.mcpEnabled ?? false,
|
||||
mcpConfig: mcpConfig ?? {},
|
||||
requiredMcpConfig: mcpConfig?.requiredMcpServers,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { applyAdminAllowlist } from './mcpUtils.js';
|
||||
import { applyAdminAllowlist, applyRequiredServers } from './mcpUtils.js';
|
||||
import type { MCPServerConfig } from '../../config/config.js';
|
||||
import { AuthProviderType } from '../../config/config.js';
|
||||
import type { RequiredMcpServerConfig } from '../types.js';
|
||||
|
||||
describe('applyAdminAllowlist', () => {
|
||||
it('should return original servers if no allowlist provided', () => {
|
||||
@@ -111,3 +113,147 @@ describe('applyAdminAllowlist', () => {
|
||||
expect(result.mcpServers['server1']?.includeTools).toEqual(['local-tool']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyRequiredServers', () => {
|
||||
it('should return original servers if no required servers provided', () => {
|
||||
const mcpServers: Record<string, MCPServerConfig> = {
|
||||
server1: { command: 'cmd1' },
|
||||
};
|
||||
const result = applyRequiredServers(mcpServers, undefined);
|
||||
expect(result.mcpServers).toEqual(mcpServers);
|
||||
expect(result.requiredServerNames).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return original servers if required servers is empty', () => {
|
||||
const mcpServers: Record<string, MCPServerConfig> = {
|
||||
server1: { command: 'cmd1' },
|
||||
};
|
||||
const result = applyRequiredServers(mcpServers, {});
|
||||
expect(result.mcpServers).toEqual(mcpServers);
|
||||
expect(result.requiredServerNames).toEqual([]);
|
||||
});
|
||||
|
||||
it('should inject required servers when no local config exists', () => {
|
||||
const mcpServers: Record<string, MCPServerConfig> = {
|
||||
'local-server': { command: 'cmd1' },
|
||||
};
|
||||
const required: Record<string, RequiredMcpServerConfig> = {
|
||||
'corp-tool': {
|
||||
url: 'https://mcp.corp.internal/tool',
|
||||
type: 'http',
|
||||
description: 'Corp compliance tool',
|
||||
},
|
||||
};
|
||||
|
||||
const result = applyRequiredServers(mcpServers, required);
|
||||
expect(Object.keys(result.mcpServers)).toContain('local-server');
|
||||
expect(Object.keys(result.mcpServers)).toContain('corp-tool');
|
||||
expect(result.requiredServerNames).toEqual(['corp-tool']);
|
||||
|
||||
const corpTool = result.mcpServers['corp-tool'];
|
||||
expect(corpTool).toBeDefined();
|
||||
expect(corpTool?.url).toBe('https://mcp.corp.internal/tool');
|
||||
expect(corpTool?.type).toBe('http');
|
||||
expect(corpTool?.description).toBe('Corp compliance tool');
|
||||
// trust defaults to true for admin-forced servers
|
||||
expect(corpTool?.trust).toBe(true);
|
||||
// stdio fields should not be set
|
||||
expect(corpTool?.command).toBeUndefined();
|
||||
expect(corpTool?.args).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should override local server with same name', () => {
|
||||
const mcpServers: Record<string, MCPServerConfig> = {
|
||||
'shared-server': {
|
||||
command: 'local-cmd',
|
||||
args: ['local-arg'],
|
||||
description: 'Local version',
|
||||
},
|
||||
};
|
||||
const required: Record<string, RequiredMcpServerConfig> = {
|
||||
'shared-server': {
|
||||
url: 'https://admin.corp/shared',
|
||||
type: 'sse',
|
||||
trust: false,
|
||||
description: 'Admin-mandated version',
|
||||
},
|
||||
};
|
||||
|
||||
const result = applyRequiredServers(mcpServers, required);
|
||||
const server = result.mcpServers['shared-server'];
|
||||
|
||||
// Admin config should completely override local
|
||||
expect(server?.url).toBe('https://admin.corp/shared');
|
||||
expect(server?.type).toBe('sse');
|
||||
expect(server?.trust).toBe(false);
|
||||
expect(server?.description).toBe('Admin-mandated version');
|
||||
// Local fields should NOT be preserved
|
||||
expect(server?.command).toBeUndefined();
|
||||
expect(server?.args).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should preserve auth configuration', () => {
|
||||
const required: Record<string, RequiredMcpServerConfig> = {
|
||||
'auth-server': {
|
||||
url: 'https://auth.corp/tool',
|
||||
type: 'http',
|
||||
authProviderType: AuthProviderType.GOOGLE_CREDENTIALS,
|
||||
oauth: {
|
||||
scopes: ['https://www.googleapis.com/auth/scope1'],
|
||||
},
|
||||
targetAudience: 'client-id.apps.googleusercontent.com',
|
||||
headers: { 'X-Custom': 'value' },
|
||||
},
|
||||
};
|
||||
|
||||
const result = applyRequiredServers({}, required);
|
||||
const server = result.mcpServers['auth-server'];
|
||||
|
||||
expect(server?.authProviderType).toBe(AuthProviderType.GOOGLE_CREDENTIALS);
|
||||
expect(server?.oauth).toEqual({
|
||||
scopes: ['https://www.googleapis.com/auth/scope1'],
|
||||
});
|
||||
expect(server?.targetAudience).toBe('client-id.apps.googleusercontent.com');
|
||||
expect(server?.headers).toEqual({ 'X-Custom': 'value' });
|
||||
});
|
||||
|
||||
it('should preserve tool filtering', () => {
|
||||
const required: Record<string, RequiredMcpServerConfig> = {
|
||||
'filtered-server': {
|
||||
url: 'https://corp/tool',
|
||||
type: 'http',
|
||||
includeTools: ['toolA', 'toolB'],
|
||||
excludeTools: ['toolC'],
|
||||
},
|
||||
};
|
||||
|
||||
const result = applyRequiredServers({}, required);
|
||||
const server = result.mcpServers['filtered-server'];
|
||||
|
||||
expect(server?.includeTools).toEqual(['toolA', 'toolB']);
|
||||
expect(server?.excludeTools).toEqual(['toolC']);
|
||||
});
|
||||
|
||||
it('should coexist with allowlisted servers', () => {
|
||||
// Simulate post-allowlist filtering
|
||||
const afterAllowlist: Record<string, MCPServerConfig> = {
|
||||
'allowed-server': {
|
||||
url: 'http://allowed',
|
||||
type: 'sse',
|
||||
trust: true,
|
||||
},
|
||||
};
|
||||
const required: Record<string, RequiredMcpServerConfig> = {
|
||||
'required-server': {
|
||||
url: 'https://required.corp/tool',
|
||||
type: 'http',
|
||||
},
|
||||
};
|
||||
|
||||
const result = applyRequiredServers(afterAllowlist, required);
|
||||
expect(Object.keys(result.mcpServers)).toHaveLength(2);
|
||||
expect(result.mcpServers['allowed-server']).toBeDefined();
|
||||
expect(result.mcpServers['required-server']).toBeDefined();
|
||||
expect(result.requiredServerNames).toEqual(['required-server']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type { MCPServerConfig } from '../../config/config.js';
|
||||
import { MCPServerConfig } from '../../config/config.js';
|
||||
import type { RequiredMcpServerConfig } from '../types.js';
|
||||
|
||||
/**
|
||||
* Applies the admin allowlist to the local MCP servers.
|
||||
@@ -65,3 +66,58 @@ export function applyAdminAllowlist(
|
||||
}
|
||||
return { mcpServers: filteredMcpServers, blockedServerNames };
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies admin-required MCP servers by injecting them into the MCP server
|
||||
* list. Required servers always take precedence over locally configured servers
|
||||
* with the same name and cannot be disabled by the user.
|
||||
*
|
||||
* @param mcpServers The current MCP servers (after allowlist filtering).
|
||||
* @param requiredServers The admin-required MCP server configurations.
|
||||
* @returns The MCP servers with required servers injected, and the list of
|
||||
* required server names for informational purposes.
|
||||
*/
|
||||
export function applyRequiredServers(
|
||||
mcpServers: Record<string, MCPServerConfig>,
|
||||
requiredServers: Record<string, RequiredMcpServerConfig> | undefined,
|
||||
): {
|
||||
mcpServers: Record<string, MCPServerConfig>;
|
||||
requiredServerNames: string[];
|
||||
} {
|
||||
if (!requiredServers || Object.keys(requiredServers).length === 0) {
|
||||
return { mcpServers, requiredServerNames: [] };
|
||||
}
|
||||
|
||||
const result: Record<string, MCPServerConfig> = { ...mcpServers };
|
||||
const requiredServerNames: string[] = [];
|
||||
|
||||
for (const [serverId, requiredConfig] of Object.entries(requiredServers)) {
|
||||
requiredServerNames.push(serverId);
|
||||
|
||||
// Convert RequiredMcpServerConfig to MCPServerConfig.
|
||||
// Required servers completely override any local config with the same name.
|
||||
result[serverId] = new MCPServerConfig(
|
||||
undefined, // command (stdio not supported for required servers)
|
||||
undefined, // args
|
||||
undefined, // env
|
||||
undefined, // cwd
|
||||
requiredConfig.url, // url
|
||||
undefined, // httpUrl (use url + type instead)
|
||||
requiredConfig.headers, // headers
|
||||
undefined, // tcp
|
||||
requiredConfig.type, // type
|
||||
requiredConfig.timeout, // timeout
|
||||
requiredConfig.trust ?? true, // trust defaults to true for admin-forced
|
||||
requiredConfig.description, // description
|
||||
requiredConfig.includeTools, // includeTools
|
||||
requiredConfig.excludeTools, // excludeTools
|
||||
undefined, // extension
|
||||
requiredConfig.oauth, // oauth
|
||||
requiredConfig.authProviderType, // authProviderType
|
||||
requiredConfig.targetAudience, // targetAudience
|
||||
requiredConfig.targetServiceAccount, // targetServiceAccount
|
||||
);
|
||||
}
|
||||
|
||||
return { mcpServers: result, requiredServerNames };
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import { AuthProviderType } from '../config/config.js';
|
||||
|
||||
export interface ClientMetadata {
|
||||
ideType?: ClientMetadataIdeType;
|
||||
@@ -359,8 +360,41 @@ const McpServerConfigSchema = z.object({
|
||||
excludeTools: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
const RequiredMcpServerOAuthSchema = z.object({
|
||||
scopes: z.array(z.string()).optional(),
|
||||
clientId: z.string().optional(),
|
||||
clientSecret: z.string().optional(),
|
||||
});
|
||||
|
||||
export const RequiredMcpServerConfigSchema = z.object({
|
||||
// Connection (required for forced servers)
|
||||
url: z.string(),
|
||||
type: z.enum(['sse', 'http']),
|
||||
|
||||
// Auth
|
||||
authProviderType: z.nativeEnum(AuthProviderType).optional(),
|
||||
oauth: RequiredMcpServerOAuthSchema.optional(),
|
||||
targetAudience: z.string().optional(),
|
||||
targetServiceAccount: z.string().optional(),
|
||||
headers: z.record(z.string()).optional(),
|
||||
|
||||
// Common
|
||||
trust: z.boolean().optional(),
|
||||
timeout: z.number().optional(),
|
||||
description: z.string().optional(),
|
||||
|
||||
// Tool filtering
|
||||
includeTools: z.array(z.string()).optional(),
|
||||
excludeTools: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
export type RequiredMcpServerConfig = z.infer<
|
||||
typeof RequiredMcpServerConfigSchema
|
||||
>;
|
||||
|
||||
export const McpConfigDefinitionSchema = z.object({
|
||||
mcpServers: z.record(McpServerConfigSchema).optional(),
|
||||
requiredMcpServers: z.record(RequiredMcpServerConfigSchema).optional(),
|
||||
});
|
||||
|
||||
export type McpConfigDefinition = z.infer<typeof McpConfigDefinitionSchema>;
|
||||
@@ -377,6 +411,7 @@ export const AdminControlsSettingsSchema = z.object({
|
||||
.object({
|
||||
mcpEnabled: z.boolean().optional(),
|
||||
mcpConfig: McpConfigDefinitionSchema.optional(),
|
||||
requiredMcpConfig: z.record(RequiredMcpServerConfigSchema).optional(),
|
||||
})
|
||||
.optional(),
|
||||
cliFeatureSetting: CliFeatureSettingSchema.optional(),
|
||||
|
||||
@@ -3104,6 +3104,35 @@ describe('Config JIT Initialization', () => {
|
||||
expect(config.getUserMemory()).toBe('Initial Memory');
|
||||
});
|
||||
|
||||
describe('isMemoryManagerEnabled', () => {
|
||||
it('should default to false', () => {
|
||||
const params: ConfigParameters = {
|
||||
sessionId: 'test-session',
|
||||
targetDir: '/tmp/test',
|
||||
debugMode: false,
|
||||
model: 'test-model',
|
||||
cwd: '/tmp/test',
|
||||
};
|
||||
|
||||
config = new Config(params);
|
||||
expect(config.isMemoryManagerEnabled()).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when experimentalMemoryManager is true', () => {
|
||||
const params: ConfigParameters = {
|
||||
sessionId: 'test-session',
|
||||
targetDir: '/tmp/test',
|
||||
debugMode: false,
|
||||
model: 'test-model',
|
||||
cwd: '/tmp/test',
|
||||
experimentalMemoryManager: true,
|
||||
};
|
||||
|
||||
config = new Config(params);
|
||||
expect(config.isMemoryManagerEnabled()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('reloadSkills', () => {
|
||||
it('should refresh disabledSkills and re-register ActivateSkillTool when skills exist', async () => {
|
||||
const mockOnReload = vi.fn().mockResolvedValue({
|
||||
|
||||
@@ -42,9 +42,11 @@ import type { HookDefinition, HookEventName } from '../hooks/types.js';
|
||||
import { FileDiscoveryService } from '../services/fileDiscoveryService.js';
|
||||
import { GitService } from '../services/gitService.js';
|
||||
import {
|
||||
createSandboxManager,
|
||||
type SandboxManager,
|
||||
NoopSandboxManager,
|
||||
} from '../services/sandboxManager.js';
|
||||
import { createSandboxManager } from '../services/sandboxManagerFactory.js';
|
||||
import { SandboxedFileSystemService } from '../services/sandboxedFileSystemService.js';
|
||||
import {
|
||||
initializeTelemetry,
|
||||
DEFAULT_TELEMETRY_TARGET,
|
||||
@@ -467,7 +469,13 @@ export interface SandboxConfig {
|
||||
enabled: boolean;
|
||||
allowedPaths?: string[];
|
||||
networkAccess?: boolean;
|
||||
command?: 'docker' | 'podman' | 'sandbox-exec' | 'runsc' | 'lxc';
|
||||
command?:
|
||||
| 'docker'
|
||||
| 'podman'
|
||||
| 'sandbox-exec'
|
||||
| 'runsc'
|
||||
| 'lxc'
|
||||
| 'windows-native';
|
||||
image?: string;
|
||||
}
|
||||
|
||||
@@ -478,7 +486,14 @@ export const ConfigSchema = z.object({
|
||||
allowedPaths: z.array(z.string()).default([]),
|
||||
networkAccess: z.boolean().default(false),
|
||||
command: z
|
||||
.enum(['docker', 'podman', 'sandbox-exec', 'runsc', 'lxc'])
|
||||
.enum([
|
||||
'docker',
|
||||
'podman',
|
||||
'sandbox-exec',
|
||||
'runsc',
|
||||
'lxc',
|
||||
'windows-native',
|
||||
])
|
||||
.optional(),
|
||||
image: z.string().optional(),
|
||||
})
|
||||
@@ -629,6 +644,7 @@ export interface ConfigParameters {
|
||||
disabledSkills?: string[];
|
||||
adminSkillsEnabled?: boolean;
|
||||
experimentalJitContext?: boolean;
|
||||
experimentalMemoryManager?: boolean;
|
||||
topicUpdateNarration?: boolean;
|
||||
toolOutputMasking?: Partial<ToolOutputMaskingConfig>;
|
||||
disableLLMCorrection?: boolean;
|
||||
@@ -853,6 +869,7 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
private readonly adminSkillsEnabled: boolean;
|
||||
|
||||
private readonly experimentalJitContext: boolean;
|
||||
private readonly experimentalMemoryManager: boolean;
|
||||
private readonly topicUpdateNarration: boolean;
|
||||
private readonly disableLLMCorrection: boolean;
|
||||
private readonly planEnabled: boolean;
|
||||
@@ -874,7 +891,6 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
this.approvedPlanPath = undefined;
|
||||
this.embeddingModel =
|
||||
params.embeddingModel ?? DEFAULT_GEMINI_EMBEDDING_MODEL;
|
||||
this.fileSystemService = new StandardFileSystemService();
|
||||
this.sandbox = params.sandbox
|
||||
? {
|
||||
enabled: params.sandbox.enabled ?? false,
|
||||
@@ -888,6 +904,21 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
};
|
||||
|
||||
this._sandboxManager = createSandboxManager(this.sandbox, params.targetDir);
|
||||
|
||||
if (
|
||||
!(this._sandboxManager instanceof NoopSandboxManager) &&
|
||||
this.sandbox.enabled
|
||||
) {
|
||||
this.fileSystemService = new SandboxedFileSystemService(
|
||||
this._sandboxManager,
|
||||
params.targetDir,
|
||||
);
|
||||
} else {
|
||||
this.fileSystemService = new StandardFileSystemService();
|
||||
}
|
||||
|
||||
this.targetDir = path.resolve(params.targetDir);
|
||||
this.folderTrust = params.folderTrust ?? false;
|
||||
this.workspaceContext = new WorkspaceContext(this.targetDir, []);
|
||||
@@ -992,6 +1023,10 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
...DEFAULT_MODEL_CONFIGS.classifierIdResolutions,
|
||||
...modelConfigServiceConfig.classifierIdResolutions,
|
||||
};
|
||||
const mergedModelChains = {
|
||||
...DEFAULT_MODEL_CONFIGS.modelChains,
|
||||
...modelConfigServiceConfig.modelChains,
|
||||
};
|
||||
|
||||
modelConfigServiceConfig = {
|
||||
// Preserve other user settings like customAliases
|
||||
@@ -1005,6 +1040,7 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
modelDefinitions: mergedModelDefinitions,
|
||||
modelIdResolutions: mergedModelIdResolutions,
|
||||
classifierIdResolutions: mergedClassifierIdResolutions,
|
||||
modelChains: mergedModelChains,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1013,6 +1049,7 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
);
|
||||
|
||||
this.experimentalJitContext = params.experimentalJitContext ?? true;
|
||||
this.experimentalMemoryManager = params.experimentalMemoryManager ?? false;
|
||||
this.topicUpdateNarration = params.topicUpdateNarration ?? false;
|
||||
this.modelSteering = params.modelSteering ?? false;
|
||||
this.injectionService = new InjectionService(() =>
|
||||
@@ -1064,7 +1101,8 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
showColor: params.shellExecutionConfig?.showColor ?? false,
|
||||
pager: params.shellExecutionConfig?.pager ?? 'cat',
|
||||
sanitizationConfig: this.sanitizationConfig,
|
||||
sandboxManager: this.sandboxManager,
|
||||
sandboxManager: this._sandboxManager,
|
||||
sandboxConfig: this.sandbox,
|
||||
};
|
||||
this.truncateToolOutputThreshold =
|
||||
params.truncateToolOutputThreshold ??
|
||||
@@ -1186,12 +1224,7 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
}
|
||||
}
|
||||
this._geminiClient = new GeminiClient(this);
|
||||
this._sandboxManager = createSandboxManager(
|
||||
params.toolSandboxing ?? false,
|
||||
this.targetDir,
|
||||
);
|
||||
this.a2aClientManager = new A2AClientManager(this);
|
||||
this.shellExecutionConfig.sandboxManager = this._sandboxManager;
|
||||
this.modelRouterService = new ModelRouterService(this);
|
||||
}
|
||||
|
||||
@@ -2157,6 +2190,10 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
return this.experimentalJitContext;
|
||||
}
|
||||
|
||||
isMemoryManagerEnabled(): boolean {
|
||||
return this.experimentalMemoryManager;
|
||||
}
|
||||
|
||||
isTopicUpdateNarrationEnabled(): boolean {
|
||||
return this.topicUpdateNarration;
|
||||
}
|
||||
@@ -3184,9 +3221,11 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
maybeRegister(ShellTool, () =>
|
||||
registry.registerTool(new ShellTool(this, this.messageBus)),
|
||||
);
|
||||
maybeRegister(MemoryTool, () =>
|
||||
registry.registerTool(new MemoryTool(this.messageBus)),
|
||||
);
|
||||
if (!this.isMemoryManagerEnabled()) {
|
||||
maybeRegister(MemoryTool, () =>
|
||||
registry.registerTool(new MemoryTool(this.messageBus)),
|
||||
);
|
||||
}
|
||||
maybeRegister(WebSearchTool, () =>
|
||||
registry.registerTool(new WebSearchTool(this, this.messageBus)),
|
||||
);
|
||||
|
||||
@@ -251,6 +251,13 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
|
||||
],
|
||||
modelDefinitions: {
|
||||
// Concrete Models
|
||||
'gemini-3.1-flash-lite-preview': {
|
||||
tier: 'flash-lite',
|
||||
family: 'gemini-3',
|
||||
isPreview: true,
|
||||
isVisible: true,
|
||||
features: { thinking: false, multimodalToolUse: true },
|
||||
},
|
||||
'gemini-3.1-pro-preview': {
|
||||
tier: 'pro',
|
||||
family: 'gemini-3',
|
||||
@@ -331,7 +338,7 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
|
||||
isPreview: true,
|
||||
isVisible: true,
|
||||
dialogDescription:
|
||||
'Let Gemini CLI decide the best model for the task: gemini-3.1-pro, gemini-3-flash',
|
||||
'Let Gemini CLI decide the best model for the task: gemini-3-pro, gemini-3-flash',
|
||||
features: { thinking: true, multimodalToolUse: false },
|
||||
},
|
||||
'auto-gemini-2.5': {
|
||||
@@ -345,6 +352,27 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
|
||||
},
|
||||
},
|
||||
modelIdResolutions: {
|
||||
'gemini-3.1-pro-preview': {
|
||||
default: 'gemini-3.1-pro-preview',
|
||||
contexts: [
|
||||
{ condition: { hasAccessToPreview: false }, target: 'gemini-2.5-pro' },
|
||||
],
|
||||
},
|
||||
'gemini-3.1-pro-preview-customtools': {
|
||||
default: 'gemini-3.1-pro-preview-customtools',
|
||||
contexts: [
|
||||
{ condition: { hasAccessToPreview: false }, target: 'gemini-2.5-pro' },
|
||||
],
|
||||
},
|
||||
'gemini-3-flash-preview': {
|
||||
default: 'gemini-3-flash-preview',
|
||||
contexts: [
|
||||
{
|
||||
condition: { hasAccessToPreview: false },
|
||||
target: 'gemini-2.5-flash',
|
||||
},
|
||||
],
|
||||
},
|
||||
'gemini-3-pro-preview': {
|
||||
default: 'gemini-3-pro-preview',
|
||||
contexts: [
|
||||
@@ -451,4 +479,120 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
|
||||
],
|
||||
},
|
||||
},
|
||||
modelChains: {
|
||||
preview: [
|
||||
{
|
||||
model: 'gemini-3-pro-preview',
|
||||
actions: {
|
||||
terminal: 'prompt',
|
||||
transient: 'prompt',
|
||||
not_found: 'prompt',
|
||||
unknown: 'prompt',
|
||||
},
|
||||
stateTransitions: {
|
||||
terminal: 'terminal',
|
||||
transient: 'terminal',
|
||||
not_found: 'terminal',
|
||||
unknown: 'terminal',
|
||||
},
|
||||
},
|
||||
{
|
||||
model: 'gemini-3-flash-preview',
|
||||
isLastResort: true,
|
||||
actions: {
|
||||
terminal: 'prompt',
|
||||
transient: 'prompt',
|
||||
not_found: 'prompt',
|
||||
unknown: 'prompt',
|
||||
},
|
||||
stateTransitions: {
|
||||
terminal: 'terminal',
|
||||
transient: 'terminal',
|
||||
not_found: 'terminal',
|
||||
unknown: 'terminal',
|
||||
},
|
||||
},
|
||||
],
|
||||
default: [
|
||||
{
|
||||
model: 'gemini-2.5-pro',
|
||||
actions: {
|
||||
terminal: 'prompt',
|
||||
transient: 'prompt',
|
||||
not_found: 'prompt',
|
||||
unknown: 'prompt',
|
||||
},
|
||||
stateTransitions: {
|
||||
terminal: 'terminal',
|
||||
transient: 'terminal',
|
||||
not_found: 'terminal',
|
||||
unknown: 'terminal',
|
||||
},
|
||||
},
|
||||
{
|
||||
model: 'gemini-2.5-flash',
|
||||
isLastResort: true,
|
||||
actions: {
|
||||
terminal: 'prompt',
|
||||
transient: 'prompt',
|
||||
not_found: 'prompt',
|
||||
unknown: 'prompt',
|
||||
},
|
||||
stateTransitions: {
|
||||
terminal: 'terminal',
|
||||
transient: 'terminal',
|
||||
not_found: 'terminal',
|
||||
unknown: 'terminal',
|
||||
},
|
||||
},
|
||||
],
|
||||
lite: [
|
||||
{
|
||||
model: 'gemini-2.5-flash-lite',
|
||||
actions: {
|
||||
terminal: 'silent',
|
||||
transient: 'silent',
|
||||
not_found: 'silent',
|
||||
unknown: 'silent',
|
||||
},
|
||||
stateTransitions: {
|
||||
terminal: 'terminal',
|
||||
transient: 'terminal',
|
||||
not_found: 'terminal',
|
||||
unknown: 'terminal',
|
||||
},
|
||||
},
|
||||
{
|
||||
model: 'gemini-2.5-flash',
|
||||
actions: {
|
||||
terminal: 'silent',
|
||||
transient: 'silent',
|
||||
not_found: 'silent',
|
||||
unknown: 'silent',
|
||||
},
|
||||
stateTransitions: {
|
||||
terminal: 'terminal',
|
||||
transient: 'terminal',
|
||||
not_found: 'terminal',
|
||||
unknown: 'terminal',
|
||||
},
|
||||
},
|
||||
{
|
||||
model: 'gemini-2.5-pro',
|
||||
isLastResort: true,
|
||||
actions: {
|
||||
terminal: 'silent',
|
||||
transient: 'silent',
|
||||
not_found: 'silent',
|
||||
unknown: 'silent',
|
||||
},
|
||||
stateTransitions: {
|
||||
terminal: 'terminal',
|
||||
transient: 'terminal',
|
||||
not_found: 'terminal',
|
||||
unknown: 'terminal',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -190,14 +190,6 @@ describe('Dynamic Configuration Parity', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('supportsModernFeatures should match legacy behavior', () => {
|
||||
for (const model of modelsToTest) {
|
||||
const legacy = supportsModernFeatures(model);
|
||||
const dynamic = supportsModernFeatures(model);
|
||||
expect(dynamic).toBe(legacy);
|
||||
}
|
||||
});
|
||||
|
||||
it('supportsMultimodalFunctionResponse should match legacy behavior', () => {
|
||||
for (const model of modelsToTest) {
|
||||
const legacy = supportsMultimodalFunctionResponse(model, legacyConfig);
|
||||
|
||||
@@ -102,11 +102,24 @@ export function resolveModel(
|
||||
config?: ModelCapabilityContext,
|
||||
): string {
|
||||
if (config?.getExperimentalDynamicModelConfiguration?.() === true) {
|
||||
return config.modelConfigService.resolveModelId(requestedModel, {
|
||||
const resolved = config.modelConfigService.resolveModelId(requestedModel, {
|
||||
useGemini3_1,
|
||||
useCustomTools: useCustomToolModel,
|
||||
hasAccessToPreview,
|
||||
});
|
||||
|
||||
if (!hasAccessToPreview && isPreviewModel(resolved, config)) {
|
||||
// Fallback for unknown preview models.
|
||||
if (resolved.includes('flash-lite')) {
|
||||
return DEFAULT_GEMINI_FLASH_LITE_MODEL;
|
||||
}
|
||||
if (resolved.includes('flash')) {
|
||||
return DEFAULT_GEMINI_FLASH_MODEL;
|
||||
}
|
||||
return DEFAULT_GEMINI_MODEL;
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
let resolved: string;
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { Config } from './config.js';
|
||||
import * as path from 'node:path';
|
||||
import * as os from 'node:os';
|
||||
|
||||
vi.mock('node:fs', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('node:fs')>();
|
||||
return {
|
||||
...actual,
|
||||
existsSync: vi.fn().mockReturnValue(true),
|
||||
statSync: vi.fn().mockReturnValue({
|
||||
isDirectory: vi.fn().mockReturnValue(true),
|
||||
}),
|
||||
realpathSync: vi.fn((p) => p),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('../utils/paths.js', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('../utils/paths.js')>();
|
||||
return {
|
||||
...actual,
|
||||
resolveToRealPath: vi.fn((p) => p),
|
||||
isSubpath: (parent: string, child: string) => child.startsWith(parent),
|
||||
};
|
||||
});
|
||||
|
||||
describe('Config Path Validation', () => {
|
||||
let config: Config;
|
||||
const targetDir = '/mock/workspace';
|
||||
const globalGeminiDir = path.join(os.homedir(), '.gemini');
|
||||
|
||||
beforeEach(() => {
|
||||
config = new Config({
|
||||
targetDir,
|
||||
sessionId: 'test-session',
|
||||
debugMode: false,
|
||||
cwd: targetDir,
|
||||
model: 'test-model',
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow access to ~/.gemini if it is added to the workspace', () => {
|
||||
const geminiMdPath = path.join(globalGeminiDir, 'GEMINI.md');
|
||||
|
||||
// Before adding, it should be denied
|
||||
expect(config.isPathAllowed(geminiMdPath)).toBe(false);
|
||||
|
||||
// Add to workspace
|
||||
config.getWorkspaceContext().addDirectory(globalGeminiDir);
|
||||
|
||||
// Now it should be allowed
|
||||
expect(config.isPathAllowed(geminiMdPath)).toBe(true);
|
||||
expect(config.validatePathAccess(geminiMdPath, 'read')).toBeNull();
|
||||
expect(config.validatePathAccess(geminiMdPath, 'write')).toBeNull();
|
||||
});
|
||||
|
||||
it('should still allow project workspace paths', () => {
|
||||
const workspacePath = path.join(targetDir, 'src/index.ts');
|
||||
expect(config.isPathAllowed(workspacePath)).toBe(true);
|
||||
expect(config.validatePathAccess(workspacePath, 'read')).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -447,7 +447,7 @@ IT IS CRITICAL TO FOLLOW THESE GUIDELINES TO AVOID EXCESSIVE TOKEN CONSUMPTION.
|
||||
- **Command Execution:** Use the 'run_shell_command' tool for running shell commands, remembering the safety rule to explain modifying commands first.
|
||||
- **Background Processes:** To run a command in the background, set the \`is_background\` parameter to true.
|
||||
- **Interactive Commands:** Always prefer non-interactive commands (e.g., using 'run once' or 'CI' flags for test runners to avoid persistent watch modes or 'git --no-pager') unless a persistent process is specifically required; however, some commands are only interactive and expect user input during their execution (e.g. ssh, vim). If you choose to execute an interactive command consider letting the user know they can press \`tab\` to focus into the shell to provide input.
|
||||
- **Remembering Facts:** Use the 'save_memory' tool to remember specific, *user-related* facts or preferences when the user explicitly asks, or when they state a clear, concise piece of information that would help personalize or streamline *your future interactions with them* (e.g., preferred coding style, common project paths they use, personal tool aliases). This tool is for user-specific information that should persist across sessions. Do *not* use it for general project context or information. If unsure whether to save something, you can ask the user, "Should I remember that for you?"
|
||||
- **Remembering Facts:** Use the 'save_memory' tool to remember specific, *user-related* facts or preferences when the user explicitly asks, or when they state a clear, concise piece of information that would help personalize or streamline *your future interactions with them* (e.g., preferred coding style, common project paths they use, personal tool aliases, or a workflow like "always lint after editing"). This tool is for user-specific information that should persist across sessions. Do *not* use it for general project context or information. If unsure whether to save something, you can ask the user, "Should I remember that for you?"
|
||||
- **Respect User Confirmations:** Most tool calls (also denoted as 'function calls') will first require confirmation from the user, where they will either approve or cancel the function call. If a user cancels a function call, respect their choice and do _not_ try to make the function call again. It is okay to request the tool call again _only_ if the user requests that same tool call on a subsequent prompt. When a user cancels a function call, assume best intentions from the user and consider inquiring if they prefer any alternative paths forward.
|
||||
|
||||
## Interaction Details
|
||||
@@ -1148,7 +1148,7 @@ IT IS CRITICAL TO FOLLOW THESE GUIDELINES TO AVOID EXCESSIVE TOKEN CONSUMPTION.
|
||||
- **Command Execution:** Use the 'run_shell_command' tool for running shell commands, remembering the safety rule to explain modifying commands first.
|
||||
- **Background Processes:** To run a command in the background, set the \`is_background\` parameter to true.
|
||||
- **Interactive Commands:** Always prefer non-interactive commands (e.g., using 'run once' or 'CI' flags for test runners to avoid persistent watch modes or 'git --no-pager') unless a persistent process is specifically required; however, some commands are only interactive and expect user input during their execution (e.g. ssh, vim). If you choose to execute an interactive command consider letting the user know they can press \`tab\` to focus into the shell to provide input.
|
||||
- **Remembering Facts:** Use the 'save_memory' tool to remember specific, *user-related* facts or preferences when the user explicitly asks, or when they state a clear, concise piece of information that would help personalize or streamline *your future interactions with them* (e.g., preferred coding style, common project paths they use, personal tool aliases). This tool is for user-specific information that should persist across sessions. Do *not* use it for general project context or information. If unsure whether to save something, you can ask the user, "Should I remember that for you?"
|
||||
- **Remembering Facts:** Use the 'save_memory' tool to remember specific, *user-related* facts or preferences when the user explicitly asks, or when they state a clear, concise piece of information that would help personalize or streamline *your future interactions with them* (e.g., preferred coding style, common project paths they use, personal tool aliases, or a workflow like "always lint after editing"). This tool is for user-specific information that should persist across sessions. Do *not* use it for general project context or information. If unsure whether to save something, you can ask the user, "Should I remember that for you?"
|
||||
- **Respect User Confirmations:** Most tool calls (also denoted as 'function calls') will first require confirmation from the user, where they will either approve or cancel the function call. If a user cancels a function call, respect their choice and do _not_ try to make the function call again. It is okay to request the tool call again _only_ if the user requests that same tool call on a subsequent prompt. When a user cancels a function call, assume best intentions from the user and consider inquiring if they prefer any alternative paths forward.
|
||||
|
||||
## Interaction Details
|
||||
@@ -1261,7 +1261,7 @@ IT IS CRITICAL TO FOLLOW THESE GUIDELINES TO AVOID EXCESSIVE TOKEN CONSUMPTION.
|
||||
- **Command Execution:** Use the 'run_shell_command' tool for running shell commands, remembering the safety rule to explain modifying commands first.
|
||||
- **Background Processes:** To run a command in the background, set the \`is_background\` parameter to true.
|
||||
- **Interactive Commands:** Always prefer non-interactive commands (e.g., using 'run once' or 'CI' flags for test runners to avoid persistent watch modes or 'git --no-pager') unless a persistent process is specifically required; however, some commands are only interactive and expect user input during their execution (e.g. ssh, vim). If you choose to execute an interactive command consider letting the user know they can press \`tab\` to focus into the shell to provide input.
|
||||
- **Remembering Facts:** Use the 'save_memory' tool to remember specific, *user-related* facts or preferences when the user explicitly asks, or when they state a clear, concise piece of information that would help personalize or streamline *your future interactions with them* (e.g., preferred coding style, common project paths they use, personal tool aliases). This tool is for user-specific information that should persist across sessions. Do *not* use it for general project context or information. If unsure whether to save something, you can ask the user, "Should I remember that for you?"
|
||||
- **Remembering Facts:** Use the 'save_memory' tool to remember specific, *user-related* facts or preferences when the user explicitly asks, or when they state a clear, concise piece of information that would help personalize or streamline *your future interactions with them* (e.g., preferred coding style, common project paths they use, personal tool aliases, or a workflow like "always lint after editing"). This tool is for user-specific information that should persist across sessions. Do *not* use it for general project context or information. If unsure whether to save something, you can ask the user, "Should I remember that for you?"
|
||||
- **Respect User Confirmations:** Most tool calls (also denoted as 'function calls') will first require confirmation from the user, where they will either approve or cancel the function call. If a user cancels a function call, respect their choice and do _not_ try to make the function call again. It is okay to request the tool call again _only_ if the user requests that same tool call on a subsequent prompt. When a user cancels a function call, assume best intentions from the user and consider inquiring if they prefer any alternative paths forward.
|
||||
|
||||
## Interaction Details
|
||||
@@ -1382,7 +1382,7 @@ IT IS CRITICAL TO FOLLOW THESE GUIDELINES TO AVOID EXCESSIVE TOKEN CONSUMPTION.
|
||||
- **Command Execution:** Use the 'run_shell_command' tool for running shell commands, remembering the safety rule to explain modifying commands first.
|
||||
- **Background Processes:** To run a command in the background, set the \`is_background\` parameter to true.
|
||||
- **Interactive Commands:** Always prefer non-interactive commands (e.g., using 'run once' or 'CI' flags for test runners to avoid persistent watch modes or 'git --no-pager') unless a persistent process is specifically required; however, some commands are only interactive and expect user input during their execution (e.g. ssh, vim). If you choose to execute an interactive command consider letting the user know they can press \`tab\` to focus into the shell to provide input.
|
||||
- **Remembering Facts:** Use the 'save_memory' tool to remember specific, *user-related* facts or preferences when the user explicitly asks, or when they state a clear, concise piece of information that would help personalize or streamline *your future interactions with them* (e.g., preferred coding style, common project paths they use, personal tool aliases). This tool is for user-specific information that should persist across sessions. Do *not* use it for general project context or information. If unsure whether to save something, you can ask the user, "Should I remember that for you?"
|
||||
- **Remembering Facts:** Use the 'save_memory' tool to remember specific, *user-related* facts or preferences when the user explicitly asks, or when they state a clear, concise piece of information that would help personalize or streamline *your future interactions with them* (e.g., preferred coding style, common project paths they use, personal tool aliases, or a workflow like "always lint after editing"). This tool is for user-specific information that should persist across sessions. Do *not* use it for general project context or information. If unsure whether to save something, you can ask the user, "Should I remember that for you?"
|
||||
- **Respect User Confirmations:** Most tool calls (also denoted as 'function calls') will first require confirmation from the user, where they will either approve or cancel the function call. If a user cancels a function call, respect their choice and do _not_ try to make the function call again. It is okay to request the tool call again _only_ if the user requests that same tool call on a subsequent prompt. When a user cancels a function call, assume best intentions from the user and consider inquiring if they prefer any alternative paths forward.
|
||||
|
||||
## Interaction Details
|
||||
@@ -1508,7 +1508,7 @@ IT IS CRITICAL TO FOLLOW THESE GUIDELINES TO AVOID EXCESSIVE TOKEN CONSUMPTION.
|
||||
- **Command Execution:** Use the 'run_shell_command' tool for running shell commands, remembering the safety rule to explain modifying commands first.
|
||||
- **Background Processes:** To run a command in the background, set the \`is_background\` parameter to true.
|
||||
- **Interactive Commands:** Always prefer non-interactive commands (e.g., using 'run once' or 'CI' flags for test runners to avoid persistent watch modes or 'git --no-pager') unless a persistent process is specifically required; however, some commands are only interactive and expect user input during their execution (e.g. ssh, vim). If you choose to execute an interactive command consider letting the user know they can press \`tab\` to focus into the shell to provide input.
|
||||
- **Remembering Facts:** Use the 'save_memory' tool to remember specific, *user-related* facts or preferences when the user explicitly asks, or when they state a clear, concise piece of information that would help personalize or streamline *your future interactions with them* (e.g., preferred coding style, common project paths they use, personal tool aliases). This tool is for user-specific information that should persist across sessions. Do *not* use it for general project context or information. If unsure whether to save something, you can ask the user, "Should I remember that for you?"
|
||||
- **Remembering Facts:** Use the 'save_memory' tool to remember specific, *user-related* facts or preferences when the user explicitly asks, or when they state a clear, concise piece of information that would help personalize or streamline *your future interactions with them* (e.g., preferred coding style, common project paths they use, personal tool aliases, or a workflow like "always lint after editing"). This tool is for user-specific information that should persist across sessions. Do *not* use it for general project context or information. If unsure whether to save something, you can ask the user, "Should I remember that for you?"
|
||||
- **Respect User Confirmations:** Most tool calls (also denoted as 'function calls') will first require confirmation from the user, where they will either approve or cancel the function call. If a user cancels a function call, respect their choice and do _not_ try to make the function call again. It is okay to request the tool call again _only_ if the user requests that same tool call on a subsequent prompt. When a user cancels a function call, assume best intentions from the user and consider inquiring if they prefer any alternative paths forward.
|
||||
|
||||
## Interaction Details
|
||||
@@ -2876,7 +2876,7 @@ IT IS CRITICAL TO FOLLOW THESE GUIDELINES TO AVOID EXCESSIVE TOKEN CONSUMPTION.
|
||||
- **Command Execution:** Use the 'run_shell_command' tool for running shell commands, remembering the safety rule to explain modifying commands first.
|
||||
- **Background Processes:** To run a command in the background, set the \`is_background\` parameter to true.
|
||||
- **Interactive Commands:** Always prefer non-interactive commands (e.g., using 'run once' or 'CI' flags for test runners to avoid persistent watch modes or 'git --no-pager') unless a persistent process is specifically required; however, some commands are only interactive and expect user input during their execution (e.g. ssh, vim). If you choose to execute an interactive command consider letting the user know they can press \`tab\` to focus into the shell to provide input.
|
||||
- **Remembering Facts:** Use the 'save_memory' tool to remember specific, *user-related* facts or preferences when the user explicitly asks, or when they state a clear, concise piece of information that would help personalize or streamline *your future interactions with them* (e.g., preferred coding style, common project paths they use, personal tool aliases). This tool is for user-specific information that should persist across sessions. Do *not* use it for general project context or information. If unsure whether to save something, you can ask the user, "Should I remember that for you?"
|
||||
- **Remembering Facts:** Use the 'save_memory' tool to remember specific, *user-related* facts or preferences when the user explicitly asks, or when they state a clear, concise piece of information that would help personalize or streamline *your future interactions with them* (e.g., preferred coding style, common project paths they use, personal tool aliases, or a workflow like "always lint after editing"). This tool is for user-specific information that should persist across sessions. Do *not* use it for general project context or information. If unsure whether to save something, you can ask the user, "Should I remember that for you?"
|
||||
- **Respect User Confirmations:** Most tool calls (also denoted as 'function calls') will first require confirmation from the user, where they will either approve or cancel the function call. If a user cancels a function call, respect their choice and do _not_ try to make the function call again. It is okay to request the tool call again _only_ if the user requests that same tool call on a subsequent prompt. When a user cancels a function call, assume best intentions from the user and consider inquiring if they prefer any alternative paths forward.
|
||||
|
||||
## Interaction Details
|
||||
@@ -3154,7 +3154,7 @@ IT IS CRITICAL TO FOLLOW THESE GUIDELINES TO AVOID EXCESSIVE TOKEN CONSUMPTION.
|
||||
- **Command Execution:** Use the 'run_shell_command' tool for running shell commands, remembering the safety rule to explain modifying commands first.
|
||||
- **Background Processes:** To run a command in the background, set the \`is_background\` parameter to true.
|
||||
- **Interactive Commands:** Always prefer non-interactive commands (e.g., using 'run once' or 'CI' flags for test runners to avoid persistent watch modes or 'git --no-pager') unless a persistent process is specifically required; however, some commands are only interactive and expect user input during their execution (e.g. ssh, vim). If you choose to execute an interactive command consider letting the user know they can press \`tab\` to focus into the shell to provide input.
|
||||
- **Remembering Facts:** Use the 'save_memory' tool to remember specific, *user-related* facts or preferences when the user explicitly asks, or when they state a clear, concise piece of information that would help personalize or streamline *your future interactions with them* (e.g., preferred coding style, common project paths they use, personal tool aliases). This tool is for user-specific information that should persist across sessions. Do *not* use it for general project context or information. If unsure whether to save something, you can ask the user, "Should I remember that for you?"
|
||||
- **Remembering Facts:** Use the 'save_memory' tool to remember specific, *user-related* facts or preferences when the user explicitly asks, or when they state a clear, concise piece of information that would help personalize or streamline *your future interactions with them* (e.g., preferred coding style, common project paths they use, personal tool aliases, or a workflow like "always lint after editing"). This tool is for user-specific information that should persist across sessions. Do *not* use it for general project context or information. If unsure whether to save something, you can ask the user, "Should I remember that for you?"
|
||||
- **Respect User Confirmations:** Most tool calls (also denoted as 'function calls') will first require confirmation from the user, where they will either approve or cancel the function call. If a user cancels a function call, respect their choice and do _not_ try to make the function call again. It is okay to request the tool call again _only_ if the user requests that same tool call on a subsequent prompt. When a user cancels a function call, assume best intentions from the user and consider inquiring if they prefer any alternative paths forward.
|
||||
|
||||
## Interaction Details
|
||||
@@ -3268,7 +3268,7 @@ IT IS CRITICAL TO FOLLOW THESE GUIDELINES TO AVOID EXCESSIVE TOKEN CONSUMPTION.
|
||||
- **Command Execution:** Use the 'run_shell_command' tool for running shell commands, remembering the safety rule to explain modifying commands first.
|
||||
- **Background Processes:** To run a command in the background, set the \`is_background\` parameter to true.
|
||||
- **Interactive Commands:** Always prefer non-interactive commands (e.g., using 'run once' or 'CI' flags for test runners to avoid persistent watch modes or 'git --no-pager') unless a persistent process is specifically required; however, some commands are only interactive and expect user input during their execution (e.g. ssh, vim). If you choose to execute an interactive command consider letting the user know they can press \`tab\` to focus into the shell to provide input.
|
||||
- **Remembering Facts:** Use the 'save_memory' tool to remember specific, *user-related* facts or preferences when the user explicitly asks, or when they state a clear, concise piece of information that would help personalize or streamline *your future interactions with them* (e.g., preferred coding style, common project paths they use, personal tool aliases). This tool is for user-specific information that should persist across sessions. Do *not* use it for general project context or information. If unsure whether to save something, you can ask the user, "Should I remember that for you?"
|
||||
- **Remembering Facts:** Use the 'save_memory' tool to remember specific, *user-related* facts or preferences when the user explicitly asks, or when they state a clear, concise piece of information that would help personalize or streamline *your future interactions with them* (e.g., preferred coding style, common project paths they use, personal tool aliases, or a workflow like "always lint after editing"). This tool is for user-specific information that should persist across sessions. Do *not* use it for general project context or information. If unsure whether to save something, you can ask the user, "Should I remember that for you?"
|
||||
- **Respect User Confirmations:** Most tool calls (also denoted as 'function calls') will first require confirmation from the user, where they will either approve or cancel the function call. If a user cancels a function call, respect their choice and do _not_ try to make the function call again. It is okay to request the tool call again _only_ if the user requests that same tool call on a subsequent prompt. When a user cancels a function call, assume best intentions from the user and consider inquiring if they prefer any alternative paths forward.
|
||||
|
||||
## Interaction Details
|
||||
@@ -3702,7 +3702,7 @@ IT IS CRITICAL TO FOLLOW THESE GUIDELINES TO AVOID EXCESSIVE TOKEN CONSUMPTION.
|
||||
- **Command Execution:** Use the 'run_shell_command' tool for running shell commands, remembering the safety rule to explain modifying commands first.
|
||||
- **Background Processes:** To run a command in the background, set the \`is_background\` parameter to true.
|
||||
- **Interactive Commands:** Always prefer non-interactive commands (e.g., using 'run once' or 'CI' flags for test runners to avoid persistent watch modes or 'git --no-pager') unless a persistent process is specifically required; however, some commands are only interactive and expect user input during their execution (e.g. ssh, vim).
|
||||
- **Remembering Facts:** Use the 'save_memory' tool to remember specific, *user-related* facts or preferences when the user explicitly asks, or when they state a clear, concise piece of information that would help personalize or streamline *your future interactions with them* (e.g., preferred coding style, common project paths they use, personal tool aliases). This tool is for user-specific information that should persist across sessions. Do *not* use it for general project context or information.
|
||||
- **Remembering Facts:** Use the 'save_memory' tool to remember specific, *user-related* facts or preferences when the user explicitly asks, or when they state a clear, concise piece of information that would help personalize or streamline *your future interactions with them* (e.g., preferred coding style, common project paths they use, personal tool aliases, or a workflow like "always lint after editing"). This tool is for user-specific information that should persist across sessions. Do *not* use it for general project context or information.
|
||||
- **Respect User Confirmations:** Most tool calls (also denoted as 'function calls') will first require confirmation from the user, where they will either approve or cancel the function call. If a user cancels a function call, respect their choice and do _not_ try to make the function call again. It is okay to request the tool call again _only_ if the user requests that same tool call on a subsequent prompt. When a user cancels a function call, assume best intentions from the user and consider inquiring if they prefer any alternative paths forward.
|
||||
|
||||
## Interaction Details
|
||||
@@ -4123,7 +4123,7 @@ IT IS CRITICAL TO FOLLOW THESE GUIDELINES TO AVOID EXCESSIVE TOKEN CONSUMPTION.
|
||||
- **Command Execution:** Use the 'run_shell_command' tool for running shell commands, remembering the safety rule to explain modifying commands first.
|
||||
- **Background Processes:** To run a command in the background, set the \`is_background\` parameter to true.
|
||||
- **Interactive Commands:** Always prefer non-interactive commands (e.g., using 'run once' or 'CI' flags for test runners to avoid persistent watch modes or 'git --no-pager') unless a persistent process is specifically required; however, some commands are only interactive and expect user input during their execution (e.g. ssh, vim). If you choose to execute an interactive command consider letting the user know they can press \`tab\` to focus into the shell to provide input.
|
||||
- **Remembering Facts:** Use the 'save_memory' tool to remember specific, *user-related* facts or preferences when the user explicitly asks, or when they state a clear, concise piece of information that would help personalize or streamline *your future interactions with them* (e.g., preferred coding style, common project paths they use, personal tool aliases). This tool is for user-specific information that should persist across sessions. Do *not* use it for general project context or information. If unsure whether to save something, you can ask the user, "Should I remember that for you?"
|
||||
- **Remembering Facts:** Use the 'save_memory' tool to remember specific, *user-related* facts or preferences when the user explicitly asks, or when they state a clear, concise piece of information that would help personalize or streamline *your future interactions with them* (e.g., preferred coding style, common project paths they use, personal tool aliases, or a workflow like "always lint after editing"). This tool is for user-specific information that should persist across sessions. Do *not* use it for general project context or information. If unsure whether to save something, you can ask the user, "Should I remember that for you?"
|
||||
- **Respect User Confirmations:** Most tool calls (also denoted as 'function calls') will first require confirmation from the user, where they will either approve or cancel the function call. If a user cancels a function call, respect their choice and do _not_ try to make the function call again. It is okay to request the tool call again _only_ if the user requests that same tool call on a subsequent prompt. When a user cancels a function call, assume best intentions from the user and consider inquiring if they prefer any alternative paths forward.
|
||||
|
||||
## Interaction Details
|
||||
|
||||
@@ -51,7 +51,7 @@ import { ClearcutLogger } from '../telemetry/clearcut-logger/clearcut-logger.js'
|
||||
import * as policyCatalog from '../availability/policyCatalog.js';
|
||||
import { LlmRole, LoopType } from '../telemetry/types.js';
|
||||
import { partToString } from '../utils/partUtils.js';
|
||||
import { coreEvents } from '../utils/events.js';
|
||||
import { coreEvents, CoreEvent } from '../utils/events.js';
|
||||
import type { MessageBus } from '../confirmation-bus/message-bus.js';
|
||||
|
||||
// Mock fs module to prevent actual file system operations during tests
|
||||
@@ -1997,6 +1997,23 @@ ${JSON.stringify(
|
||||
);
|
||||
});
|
||||
|
||||
it('should update system instruction when MemoryChanged event is emitted', async () => {
|
||||
vi.mocked(mockConfig.getSystemInstructionMemory).mockReturnValue(
|
||||
'Updated Memory',
|
||||
);
|
||||
|
||||
const { getCoreSystemPrompt } = await import('./prompts.js');
|
||||
const mockGetCoreSystemPrompt = vi.mocked(getCoreSystemPrompt);
|
||||
mockGetCoreSystemPrompt.mockClear();
|
||||
|
||||
coreEvents.emit(CoreEvent.MemoryChanged, { fileCount: 2 });
|
||||
|
||||
expect(mockGetCoreSystemPrompt).toHaveBeenCalledWith(
|
||||
mockConfig,
|
||||
'Updated Memory',
|
||||
);
|
||||
});
|
||||
|
||||
it('should recursively call sendMessageStream with "Please continue." when InvalidStream event is received for Gemini 2 models', async () => {
|
||||
vi.spyOn(client['config'], 'getContinueOnFailedApiCall').mockReturnValue(
|
||||
true,
|
||||
|
||||
@@ -117,6 +117,7 @@ export class GeminiClient {
|
||||
this.lastPromptId = this.config.getSessionId();
|
||||
|
||||
coreEvents.on(CoreEvent.ModelChanged, this.handleModelChanged);
|
||||
coreEvents.on(CoreEvent.MemoryChanged, this.handleMemoryChanged);
|
||||
}
|
||||
|
||||
private get config(): Config {
|
||||
@@ -127,6 +128,10 @@ export class GeminiClient {
|
||||
this.currentSequenceModel = null;
|
||||
};
|
||||
|
||||
private handleMemoryChanged = () => {
|
||||
this.updateSystemInstruction();
|
||||
};
|
||||
|
||||
// Hook state to deduplicate BeforeAgent calls and track response for
|
||||
// AfterAgent
|
||||
private hookStateMap = new Map<
|
||||
@@ -306,6 +311,7 @@ export class GeminiClient {
|
||||
|
||||
dispose() {
|
||||
coreEvents.off(CoreEvent.ModelChanged, this.handleModelChanged);
|
||||
coreEvents.off(CoreEvent.MemoryChanged, this.handleMemoryChanged);
|
||||
}
|
||||
|
||||
async resumeChat(
|
||||
|
||||
@@ -96,6 +96,7 @@ describe('Core System Prompt (prompts.ts)', () => {
|
||||
isInteractive: vi.fn().mockReturnValue(true),
|
||||
isInteractiveShellEnabled: vi.fn().mockReturnValue(true),
|
||||
isTopicUpdateNarrationEnabled: vi.fn().mockReturnValue(false),
|
||||
isMemoryManagerEnabled: vi.fn().mockReturnValue(false),
|
||||
isAgentsEnabled: vi.fn().mockReturnValue(false),
|
||||
getPreviewFeatures: vi.fn().mockReturnValue(true),
|
||||
getModel: vi.fn().mockReturnValue(DEFAULT_GEMINI_MODEL_AUTO),
|
||||
@@ -423,6 +424,7 @@ describe('Core System Prompt (prompts.ts)', () => {
|
||||
isInteractive: vi.fn().mockReturnValue(false),
|
||||
isInteractiveShellEnabled: vi.fn().mockReturnValue(false),
|
||||
isTopicUpdateNarrationEnabled: vi.fn().mockReturnValue(false),
|
||||
isMemoryManagerEnabled: vi.fn().mockReturnValue(false),
|
||||
isAgentsEnabled: vi.fn().mockReturnValue(false),
|
||||
getModel: vi.fn().mockReturnValue('auto'),
|
||||
getActiveModel: vi.fn().mockReturnValue(PREVIEW_GEMINI_MODEL),
|
||||
|
||||
@@ -126,6 +126,8 @@ export * from './services/gitService.js';
|
||||
export * from './services/FolderTrustDiscoveryService.js';
|
||||
export * from './services/chatRecordingService.js';
|
||||
export * from './services/fileSystemService.js';
|
||||
export * from './services/sandboxedFileSystemService.js';
|
||||
export * from './services/windowsSandboxManager.js';
|
||||
export * from './services/sessionSummaryUtils.js';
|
||||
export * from './services/contextManager.js';
|
||||
export * from './services/trackerService.js';
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { PolicyEngine } from './policy-engine.js';
|
||||
import { loadPoliciesFromToml } from './toml-loader.js';
|
||||
import { PolicyDecision, ApprovalMode } from './types.js';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
describe('Memory Manager Policy', () => {
|
||||
let engine: PolicyEngine;
|
||||
|
||||
beforeEach(async () => {
|
||||
const policiesDir = path.join(__dirname, 'policies');
|
||||
const result = await loadPoliciesFromToml([policiesDir], () => 1);
|
||||
engine = new PolicyEngine({
|
||||
rules: result.rules,
|
||||
approvalMode: ApprovalMode.DEFAULT,
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow save_memory to read ~/.gemini/GEMINI.md', async () => {
|
||||
const toolCall = {
|
||||
name: 'read_file',
|
||||
args: { file_path: '~/.gemini/GEMINI.md' },
|
||||
};
|
||||
const result = await engine.check(
|
||||
toolCall,
|
||||
undefined,
|
||||
undefined,
|
||||
'save_memory',
|
||||
);
|
||||
expect(result.decision).toBe(PolicyDecision.ALLOW);
|
||||
});
|
||||
|
||||
it('should allow save_memory to write ~/.gemini/GEMINI.md', async () => {
|
||||
const toolCall = {
|
||||
name: 'write_file',
|
||||
args: { file_path: '~/.gemini/GEMINI.md', content: 'test' },
|
||||
};
|
||||
const result = await engine.check(
|
||||
toolCall,
|
||||
undefined,
|
||||
undefined,
|
||||
'save_memory',
|
||||
);
|
||||
expect(result.decision).toBe(PolicyDecision.ALLOW);
|
||||
});
|
||||
|
||||
it('should allow save_memory to list ~/.gemini/', async () => {
|
||||
const toolCall = {
|
||||
name: 'list_directory',
|
||||
args: { dir_path: '~/.gemini/' },
|
||||
};
|
||||
const result = await engine.check(
|
||||
toolCall,
|
||||
undefined,
|
||||
undefined,
|
||||
'save_memory',
|
||||
);
|
||||
expect(result.decision).toBe(PolicyDecision.ALLOW);
|
||||
});
|
||||
|
||||
it('should fall through to global allow rule for save_memory reading non-.gemini files', async () => {
|
||||
const toolCall = {
|
||||
name: 'read_file',
|
||||
args: { file_path: '/etc/passwd' },
|
||||
};
|
||||
const result = await engine.check(
|
||||
toolCall,
|
||||
undefined,
|
||||
undefined,
|
||||
'save_memory',
|
||||
);
|
||||
// The memory-manager policy only matches .gemini/ paths.
|
||||
// Other paths fall through to the global read_file allow rule (priority 50).
|
||||
expect(result.decision).toBe(PolicyDecision.ALLOW);
|
||||
});
|
||||
|
||||
it('should not match paths where .gemini is a substring (e.g. not.gemini)', async () => {
|
||||
const toolCall = {
|
||||
name: 'read_file',
|
||||
args: { file_path: '/tmp/not.gemini/evil' },
|
||||
};
|
||||
const result = await engine.check(
|
||||
toolCall,
|
||||
undefined,
|
||||
undefined,
|
||||
'save_memory',
|
||||
);
|
||||
// The tighter argsPattern requires .gemini/ to be preceded by start-of-string
|
||||
// or a path separator, so "not.gemini/" should NOT match the memory-manager rule.
|
||||
// It falls through to the global read_file allow rule instead.
|
||||
expect(result.decision).toBe(PolicyDecision.ALLOW);
|
||||
});
|
||||
|
||||
it('should fall through to global allow rule for other agents accessing ~/.gemini/', async () => {
|
||||
const toolCall = {
|
||||
name: 'read_file',
|
||||
args: { file_path: '~/.gemini/GEMINI.md' },
|
||||
};
|
||||
const result = await engine.check(
|
||||
toolCall,
|
||||
undefined,
|
||||
undefined,
|
||||
'other_agent',
|
||||
);
|
||||
// The memory-manager policy rule (priority 100) only applies to 'save_memory'.
|
||||
// Other agents fall through to the global read_file allow rule (priority 50).
|
||||
expect(result.decision).toBe(PolicyDecision.ALLOW);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
# Policy for Memory Manager Agent
|
||||
# Allows the save_memory agent to manage memories in the ~/.gemini/ folder.
|
||||
|
||||
[[rule]]
|
||||
subagent = "save_memory"
|
||||
toolName = ["read_file", "write_file", "replace", "list_directory", "glob", "grep_search"]
|
||||
decision = "allow"
|
||||
priority = 100
|
||||
argsPattern = "(^|.*/)\\.gemini/.*"
|
||||
deny_message = "Memory Manager is only allowed to access the .gemini folder."
|
||||
@@ -61,6 +61,7 @@ describe('PromptProvider', () => {
|
||||
isInteractive: vi.fn().mockReturnValue(true),
|
||||
isInteractiveShellEnabled: vi.fn().mockReturnValue(true),
|
||||
isTopicUpdateNarrationEnabled: vi.fn().mockReturnValue(false),
|
||||
isMemoryManagerEnabled: vi.fn().mockReturnValue(false),
|
||||
getSkillManager: vi.fn().mockReturnValue({
|
||||
getSkills: vi.fn().mockReturnValue([]),
|
||||
}),
|
||||
|
||||
@@ -192,6 +192,7 @@ export class PromptProvider {
|
||||
interactiveShellEnabled: context.config.isInteractiveShellEnabled(),
|
||||
topicUpdateNarration:
|
||||
context.config.isTopicUpdateNarrationEnabled(),
|
||||
memoryManagerEnabled: context.config.isMemoryManagerEnabled(),
|
||||
}),
|
||||
),
|
||||
sandbox: this.withSection('sandbox', () => getSandboxMode()),
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { renderOperationalGuidelines } from './snippets.js';
|
||||
|
||||
describe('renderOperationalGuidelines - memoryManagerEnabled', () => {
|
||||
const baseOptions = {
|
||||
interactive: true,
|
||||
interactiveShellEnabled: false,
|
||||
topicUpdateNarration: false,
|
||||
memoryManagerEnabled: false,
|
||||
};
|
||||
|
||||
it('should include standard memory tool guidance when memoryManagerEnabled is false', () => {
|
||||
const result = renderOperationalGuidelines(baseOptions);
|
||||
expect(result).toContain('save_memory');
|
||||
expect(result).toContain('persistent user-related information');
|
||||
expect(result).not.toContain('subagent');
|
||||
});
|
||||
|
||||
it('should include subagent memory guidance when memoryManagerEnabled is true', () => {
|
||||
const result = renderOperationalGuidelines({
|
||||
...baseOptions,
|
||||
memoryManagerEnabled: true,
|
||||
});
|
||||
expect(result).toContain('save_memory');
|
||||
expect(result).toContain('subagent');
|
||||
expect(result).not.toContain('persistent user-related information');
|
||||
});
|
||||
});
|
||||
@@ -67,6 +67,7 @@ export interface OperationalGuidelinesOptions {
|
||||
isGemini3: boolean;
|
||||
enableShellEfficiency: boolean;
|
||||
interactiveShellEnabled: boolean;
|
||||
memoryManagerEnabled: boolean;
|
||||
}
|
||||
|
||||
export type SandboxMode = 'macos-seatbelt' | 'generic' | 'outside';
|
||||
@@ -647,8 +648,12 @@ function toolUsageInteractive(
|
||||
function toolUsageRememberingFacts(
|
||||
options: OperationalGuidelinesOptions,
|
||||
): string {
|
||||
if (options.memoryManagerEnabled) {
|
||||
return `
|
||||
- **Memory Tool:** You MUST use the '${MEMORY_TOOL_NAME}' tool to proactively record facts, preferences, and workflows that apply across all sessions. Whenever the user explicitly tells you to "remember" something, or when they state a preference or workflow (like "always lint after editing"), you MUST immediately call the save_memory subagent. Never save transient session state. Do not use memory to store summaries of code changes, bug fixes, or findings discovered during a task; this tool is strictly for persistent general knowledge.`;
|
||||
}
|
||||
const base = `
|
||||
- **Remembering Facts:** Use the '${MEMORY_TOOL_NAME}' tool to remember specific, *user-related* facts or preferences when the user explicitly asks, or when they state a clear, concise piece of information that would help personalize or streamline *your future interactions with them* (e.g., preferred coding style, common project paths they use, personal tool aliases). This tool is for user-specific information that should persist across sessions. Do *not* use it for general project context or information.`;
|
||||
- **Remembering Facts:** Use the '${MEMORY_TOOL_NAME}' tool to remember specific, *user-related* facts or preferences when the user explicitly asks, or when they state a clear, concise piece of information that would help personalize or streamline *your future interactions with them* (e.g., preferred coding style, common project paths they use, personal tool aliases, or a workflow like "always lint after editing"). This tool is for user-specific information that should persist across sessions. Do *not* use it for general project context or information.`;
|
||||
const suffix = options.interactive
|
||||
? ' If unsure whether to save something, you can ask the user, "Should I remember that for you?"'
|
||||
: '';
|
||||
|
||||
@@ -79,6 +79,7 @@ export interface OperationalGuidelinesOptions {
|
||||
interactive: boolean;
|
||||
interactiveShellEnabled: boolean;
|
||||
topicUpdateNarration: boolean;
|
||||
memoryManagerEnabled: boolean;
|
||||
}
|
||||
|
||||
export type SandboxMode = 'macos-seatbelt' | 'generic' | 'outside';
|
||||
@@ -777,6 +778,10 @@ function toolUsageInteractive(
|
||||
function toolUsageRememberingFacts(
|
||||
options: OperationalGuidelinesOptions,
|
||||
): string {
|
||||
if (options.memoryManagerEnabled) {
|
||||
return `
|
||||
- **Memory Tool:** You MUST use ${formatToolName(MEMORY_TOOL_NAME)} to proactively record facts, preferences, and workflows that apply across all sessions. Whenever the user explicitly tells you to "remember" something, or when they state a preference or workflow (like "always lint after editing"), you MUST immediately call the save_memory subagent. Never save transient session state. Do not use memory to store summaries of code changes, bug fixes, or findings discovered during a task; this tool is strictly for persistent general knowledge.`;
|
||||
}
|
||||
const base = `
|
||||
- **Memory Tool:** Use ${formatToolName(MEMORY_TOOL_NAME)} only for global user preferences, personal facts, or high-level information that applies across all sessions. Never save workspace-specific context, local file paths, or transient session state. Do not use memory to store summaries of code changes, bug fixes, or findings discovered during a task; this tool is for persistent user-related information only.`;
|
||||
const suffix = options.interactive
|
||||
|
||||
@@ -363,6 +363,7 @@ export class Scheduler {
|
||||
callId: request.callId,
|
||||
schedulerId: this.schedulerId,
|
||||
parentCallId: this.parentCallId,
|
||||
subagent: this.subagent,
|
||||
},
|
||||
() => {
|
||||
try {
|
||||
@@ -670,6 +671,7 @@ export class Scheduler {
|
||||
callId: activeCall.request.callId,
|
||||
schedulerId: this.schedulerId,
|
||||
parentCallId: this.parentCallId,
|
||||
subagent: this.subagent,
|
||||
},
|
||||
() =>
|
||||
this.executor.execute({
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user