Compare commits

..

9 Commits

Author SHA1 Message Date
Dmitry Lyalin da258412df Merge branch 'main' into workspace-command-scope-20737 2026-03-10 18:56:50 -04:00
Dmitry Lyalin a9532b3ac4 Merge branch 'main' into workspace-command-scope-20737 2026-03-10 17:02:26 -04:00
Dmitry Lyalin dba383ef3c Merge branch 'main' into workspace-command-scope-20737 2026-03-10 15:20:46 -04:00
Dmitry Lyalin 31819197dd Merge branch 'main' into workspace-command-scope-20737 2026-03-10 12:49:15 -04:00
Dmitry Lyalin 1f07e5b9e3 Merge branch 'main' into workspace-command-scope-20737 2026-03-10 11:00:47 -04:00
Dmitry Lyalin 73da95c6c7 Merge branch 'main' into workspace-command-scope-20737 2026-03-10 10:35:49 -04:00
Dmitry Lyalin c8d422986f Regenerate settings docs 2026-03-10 10:34:55 -04:00
Dmitry Lyalin b3ff5454ab Merge branch 'main' into workspace-command-scope-20737 2026-03-10 10:11:37 -04:00
Dmitry Lyalin 8b5102e55e Rename directory command to workspace 2026-03-10 09:46:48 -04:00
461 changed files with 6407 additions and 19261 deletions
@@ -347,36 +347,6 @@ async function run() {
});
}
}
// Remove status/need-triage from maintainer-only issues since they
// don't need community triage. We always attempt removal rather than
// checking the (potentially stale) label snapshot, because the
// issue-opened-labeler workflow runs concurrently and may add the
// label after our snapshot was taken.
if (isDryRun) {
console.log(
`[DRY RUN] Would remove status/need-triage from ${issueKey}`,
);
} else {
try {
await octokit.rest.issues.removeLabel({
owner: issueInfo.owner,
repo: issueInfo.repo,
issue_number: issueInfo.number,
name: 'status/need-triage',
});
console.log(`Removed status/need-triage from ${issueKey}`);
} catch (removeError) {
// 404 means the label wasn't present — that's fine.
if (removeError.status === 404) {
console.log(
`status/need-triage not present on ${issueKey}, skipping.`,
);
} else {
throw removeError;
}
}
}
} catch (error) {
console.error(`Error processing label for ${issueKey}: ${error.message}`);
}
-2
View File
@@ -95,8 +95,6 @@ jobs:
This PR contains the auto-generated changelog for the ${{ steps.release_info.outputs.VERSION }} release.
Please review and merge.
Related to #18505
branch: 'changelog-${{ steps.release_info.outputs.VERSION }}'
base: 'main'
team-reviewers: 'gemini-cli-docs, gemini-cli-maintainers'
-19
View File
@@ -18,25 +18,6 @@ on GitHub.
| [Preview](preview.md) | Experimental features ready for early feedback. |
| [Stable](latest.md) | Stable, recommended for general use. |
## Announcements: v0.33.0 - 2026-03-11
- **Agent Architecture Enhancements:** Introduced HTTP authentication for A2A
remote agents and authenticated A2A agent card discovery
([#20510](https://github.com/google-gemini/gemini-cli/pull/20510) by
@SandyTao520, [#20622](https://github.com/google-gemini/gemini-cli/pull/20622)
by @SandyTao520).
- **Plan Mode Updates:** Expanded Plan Mode with built-in research subagents,
annotation support for feedback, and a new `copy` subcommand
([#20972](https://github.com/google-gemini/gemini-cli/pull/20972) by @Adib234,
[#20988](https://github.com/google-gemini/gemini-cli/pull/20988) by
@ruomengz).
- **CLI UX & Admin Controls:** Redesigned the header to be compact with an ASCII
icon, inverted context window display to show usage, and enabled a 30-day
default retention for chat history
([#18713](https://github.com/google-gemini/gemini-cli/pull/18713) by
@keithguerin, [#20853](https://github.com/google-gemini/gemini-cli/pull/20853)
by @skeshive).
## Announcements: v0.32.0 - 2026-03-03
- **Generalist Agent:** The generalist agent is now enabled to improve task
+191 -217
View File
@@ -1,6 +1,6 @@
# Latest stable release: v0.33.1
# Latest stable release: v0.32.1
Released: March 12, 2026
Released: March 4, 2026
For most users, our latest stable release is the recommended release. Install
the latest stable version with:
@@ -11,224 +11,198 @@ npm install -g @google/gemini-cli
## Highlights
- **Agent Architecture Enhancements:** Introduced HTTP authentication support
for A2A remote agents, authenticated A2A agent card discovery, and directly
indicated auth-required states.
- **Plan Mode Updates:** Expanded Plan Mode capabilities with built-in research
subagents, annotation support for feedback during iteration, and a new `copy`
subcommand.
- **CLI UX Improvements:** Redesigned the header to be compact with an ASCII
icon, inverted the context window display to show usage, and allowed sub-agent
confirmation requests in the UI while preventing background flicker.
- **ACP & MCP Integrations:** Implemented slash command handling in ACP for
`/memory`, `/init`, `/extensions`, and `/restore`, added an MCPOAuthProvider,
and introduced a `set models` interface for ACP.
- **Admin & Core Stability:** Enabled a 30-day default retention for chat
history, added tool name validation in TOML policy files, and improved tool
parameter extraction.
- **Plan Mode Enhancements**: Significant updates to Plan Mode, including the
ability to open and modify plans in an external editor, adaptations for
complex tasks with multi-select options, and integration tests for plan mode.
- **Agent and Steering Improvements**: The generalist agent has been enabled to
enhance task delegation, model steering is now supported directly within the
workspace, and contiguous parallel admission is enabled for `Kind.Agent`
tools.
- **Interactive Shell**: Interactive shell autocompletion has been introduced,
significantly enhancing the user experience.
- **Core Stability and Performance**: Extensions are now loaded in parallel,
fetch timeouts have been increased, robust A2A streaming reassembly was
implemented, and orphaned processes when terminal closes have been prevented.
- **Billing and Quota Handling**: Implemented G1 AI credits overage flow with
billing telemetry and added support for quota error fallbacks across all
authentication types.
## What's Changed
- fix(patch): cherry-pick 8432bce to release/v0.33.0-pr-22069 to patch version
v0.33.0 and create version 0.33.1 by @gemini-cli-robot in
[#22206](https://github.com/google-gemini/gemini-cli/pull/22206)
- Docs: Update model docs to remove Preview Features. by @jkcinouye in
[#20084](https://github.com/google-gemini/gemini-cli/pull/20084)
- docs: fix typo in installation documentation by @AdityaSharma-Git3207 in
[#20153](https://github.com/google-gemini/gemini-cli/pull/20153)
- docs: add Windows PowerShell equivalents for environments and scripting by
@scidomino in [#20333](https://github.com/google-gemini/gemini-cli/pull/20333)
- fix(core): parse raw ASCII buffer strings in Gaxios errors by @sehoon38 in
[#20626](https://github.com/google-gemini/gemini-cli/pull/20626)
- chore(release): bump version to 0.33.0-nightly.20260227.ba149afa0 by @galz10
in [#20637](https://github.com/google-gemini/gemini-cli/pull/20637)
- fix(github): use robot PAT for automated PRs to pass CLA check by @galz10 in
[#20641](https://github.com/google-gemini/gemini-cli/pull/20641)
- chore/release: bump version to 0.33.0-nightly.20260228.1ca5c05d0 by
@gemini-cli-robot in
[#20644](https://github.com/google-gemini/gemini-cli/pull/20644)
- Changelog for v0.31.0 by @gemini-cli-robot in
[#20634](https://github.com/google-gemini/gemini-cli/pull/20634)
- fix: use full paths for ACP diff payloads by @JagjeevanAK in
[#19539](https://github.com/google-gemini/gemini-cli/pull/19539)
- Changelog for v0.32.0-preview.0 by @gemini-cli-robot in
[#20627](https://github.com/google-gemini/gemini-cli/pull/20627)
- fix: acp/zed race condition between MCP initialisation and prompt by
@kartikangiras in
[#20205](https://github.com/google-gemini/gemini-cli/pull/20205)
- fix(cli): reset themeManager between tests to ensure isolation by
@NTaylorMullen in
[#20598](https://github.com/google-gemini/gemini-cli/pull/20598)
- refactor(core): Extract tool parameter names as constants by @SandyTao520 in
[#20460](https://github.com/google-gemini/gemini-cli/pull/20460)
- fix(cli): resolve autoThemeSwitching when background hasn't changed but theme
mismatches by @sehoon38 in
[#20706](https://github.com/google-gemini/gemini-cli/pull/20706)
- feat(skills): add github-issue-creator skill by @sehoon38 in
[#20709](https://github.com/google-gemini/gemini-cli/pull/20709)
- fix(cli): allow sub-agent confirmation requests in UI while preventing
background flicker by @abhipatel12 in
[#20722](https://github.com/google-gemini/gemini-cli/pull/20722)
- Merge User and Agent Card Descriptions #20849 by @adamfweidman in
[#20850](https://github.com/google-gemini/gemini-cli/pull/20850)
- fix(core): reduce LLM-based loop detection false positives by @SandyTao520 in
[#20701](https://github.com/google-gemini/gemini-cli/pull/20701)
- fix(plan): deflake plan mode integration tests by @Adib234 in
[#20477](https://github.com/google-gemini/gemini-cli/pull/20477)
- Add /unassign support by @scidomino in
[#20864](https://github.com/google-gemini/gemini-cli/pull/20864)
- feat(core): implement HTTP authentication support for A2A remote agents by
- fix(patch): cherry-pick 0659ad1 to release/v0.32.0-pr-21042 to patch version
v0.32.0 and create version 0.32.1 by @gemini-cli-robot in
[#21048](https://github.com/google-gemini/gemini-cli/pull/21048)
- feat(plan): add integration tests for plan mode by @Adib234 in
[#20214](https://github.com/google-gemini/gemini-cli/pull/20214)
- fix(acp): update auth handshake to spec by @skeshive in
[#19725](https://github.com/google-gemini/gemini-cli/pull/19725)
- feat(core): implement robust A2A streaming reassembly and fix task continuity
by @adamfweidman in
[#20091](https://github.com/google-gemini/gemini-cli/pull/20091)
- feat(cli): load extensions in parallel by @scidomino in
[#20229](https://github.com/google-gemini/gemini-cli/pull/20229)
- Plumb the maxAttempts setting through Config args by @kevinjwang1 in
[#20239](https://github.com/google-gemini/gemini-cli/pull/20239)
- fix(cli): skip 404 errors in setup-github file downloads by @h30s in
[#20287](https://github.com/google-gemini/gemini-cli/pull/20287)
- fix(cli): expose model.name setting in settings dialog for persistence by
@achaljhawar in
[#19605](https://github.com/google-gemini/gemini-cli/pull/19605)
- docs: remove legacy cmd examples in favor of powershell by @scidomino in
[#20323](https://github.com/google-gemini/gemini-cli/pull/20323)
- feat(core): Enable model steering in workspace. by @joshualitt in
[#20343](https://github.com/google-gemini/gemini-cli/pull/20343)
- fix: remove trailing comma in issue triage workflow settings json by @Nixxx19
in [#20265](https://github.com/google-gemini/gemini-cli/pull/20265)
- feat(core): implement task tracker foundation and service by @anj-s in
[#19464](https://github.com/google-gemini/gemini-cli/pull/19464)
- test: support tests that include color information by @jacob314 in
[#20220](https://github.com/google-gemini/gemini-cli/pull/20220)
- feat(core): introduce Kind.Agent for sub-agent classification by @abhipatel12
in [#20369](https://github.com/google-gemini/gemini-cli/pull/20369)
- Changelog for v0.30.0 by @gemini-cli-robot in
[#20252](https://github.com/google-gemini/gemini-cli/pull/20252)
- Update changelog workflow to reject nightly builds by @g-samroberts in
[#20248](https://github.com/google-gemini/gemini-cli/pull/20248)
- Changelog for v0.31.0-preview.0 by @gemini-cli-robot in
[#20249](https://github.com/google-gemini/gemini-cli/pull/20249)
- feat(cli): hide workspace policy update dialog and auto-accept by default by
@Abhijit-2592 in
[#20351](https://github.com/google-gemini/gemini-cli/pull/20351)
- feat(core): rename grep_search include parameter to include_pattern by
@SandyTao520 in
[#20510](https://github.com/google-gemini/gemini-cli/pull/20510)
- feat(core): centralize read_file limits and update gemini-3 description by
@aishaneeshah in
[#20619](https://github.com/google-gemini/gemini-cli/pull/20619)
- Do not block CI on evals by @gundermanc in
[#20870](https://github.com/google-gemini/gemini-cli/pull/20870)
- document node limitation for shift+tab by @scidomino in
[#20877](https://github.com/google-gemini/gemini-cli/pull/20877)
- Add install as an option when extension is selected. by @DavidAPierce in
[#20358](https://github.com/google-gemini/gemini-cli/pull/20358)
- Update CODEOWNERS for README.md reviewers by @g-samroberts in
[#20860](https://github.com/google-gemini/gemini-cli/pull/20860)
- feat(core): truncate large MCP tool output by @SandyTao520 in
[#19365](https://github.com/google-gemini/gemini-cli/pull/19365)
- Subagent activity UX. by @gundermanc in
[#17570](https://github.com/google-gemini/gemini-cli/pull/17570)
- style(cli) : Dialog pattern for /hooks Command by @AbdulTawabJuly in
[#17930](https://github.com/google-gemini/gemini-cli/pull/17930)
- feat: redesign header to be compact with ASCII icon by @keithguerin in
[#18713](https://github.com/google-gemini/gemini-cli/pull/18713)
- fix(core): ensure subagents use qualified MCP tool names by @abhipatel12 in
[#20801](https://github.com/google-gemini/gemini-cli/pull/20801)
- feat(core): support authenticated A2A agent card discovery by @SandyTao520 in
[#20622](https://github.com/google-gemini/gemini-cli/pull/20622)
- refactor(cli): fully remove React anti patterns, improve type safety and fix
UX oversights in SettingsDialog.tsx by @psinha40898 in
[#18963](https://github.com/google-gemini/gemini-cli/pull/18963)
- Adding MCPOAuthProvider implementing the MCPSDK OAuthClientProvider by
@Nayana-Parameswarappa in
[#20121](https://github.com/google-gemini/gemini-cli/pull/20121)
- feat(core): add tool name validation in TOML policy files by @allenhutchison
in [#19281](https://github.com/google-gemini/gemini-cli/pull/19281)
- docs: fix broken markdown links in main README.md by @Hamdanbinhashim in
[#20300](https://github.com/google-gemini/gemini-cli/pull/20300)
- refactor(core): replace manual syncPlanModeTools with declarative policy rules
by @jerop in [#20596](https://github.com/google-gemini/gemini-cli/pull/20596)
- fix(core): increase default headers timeout to 5 minutes by @gundermanc in
[#20890](https://github.com/google-gemini/gemini-cli/pull/20890)
- feat(admin): enable 30 day default retention for chat history & remove warning
by @skeshive in
[#20853](https://github.com/google-gemini/gemini-cli/pull/20853)
- feat(plan): support annotating plans with feedback for iteration by @Adib234
in [#20876](https://github.com/google-gemini/gemini-cli/pull/20876)
- Add some dos and don'ts to behavioral evals README. by @gundermanc in
[#20629](https://github.com/google-gemini/gemini-cli/pull/20629)
- fix(core): skip telemetry logging for AbortError exceptions by @yunaseoul in
[#19477](https://github.com/google-gemini/gemini-cli/pull/19477)
- fix(core): restrict "System: Please continue" invalid stream retry to Gemini 2
models by @SandyTao520 in
[#20897](https://github.com/google-gemini/gemini-cli/pull/20897)
- ci(evals): only run evals in CI if prompts or tools changed by @gundermanc in
[#20898](https://github.com/google-gemini/gemini-cli/pull/20898)
- Build binary by @aswinashok44 in
[#18933](https://github.com/google-gemini/gemini-cli/pull/18933)
- Code review fixes as a pr by @jacob314 in
[#20612](https://github.com/google-gemini/gemini-cli/pull/20612)
- fix(ci): handle empty APP_ID in stale PR closer by @bdmorgan in
[#20919](https://github.com/google-gemini/gemini-cli/pull/20919)
- feat(cli): invert context window display to show usage by @keithguerin in
[#20071](https://github.com/google-gemini/gemini-cli/pull/20071)
- fix(plan): clean up session directories and plans on deletion by @jerop in
[#20914](https://github.com/google-gemini/gemini-cli/pull/20914)
- fix(core): enforce optionality for API response fields in code_assist by
@sehoon38 in [#20714](https://github.com/google-gemini/gemini-cli/pull/20714)
- feat(extensions): add support for plan directory in extension manifest by
@mahimashanware in
[#20354](https://github.com/google-gemini/gemini-cli/pull/20354)
- feat(plan): enable built-in research subagents in plan mode by @Adib234 in
[#20972](https://github.com/google-gemini/gemini-cli/pull/20972)
- feat(agents): directly indicate auth required state by @adamfweidman in
[#20986](https://github.com/google-gemini/gemini-cli/pull/20986)
- fix(cli): wait for background auto-update before relaunching by @scidomino in
[#20904](https://github.com/google-gemini/gemini-cli/pull/20904)
- fix: pre-load @scripts/copy_files.js references from external editor prompts
by @kartikangiras in
[#20963](https://github.com/google-gemini/gemini-cli/pull/20963)
- feat(evals): add behavioral evals for ask_user tool by @Adib234 in
[#20620](https://github.com/google-gemini/gemini-cli/pull/20620)
- refactor common settings logic for skills,agents by @ishaanxgupta in
[#17490](https://github.com/google-gemini/gemini-cli/pull/17490)
- Update docs-writer skill with new resource by @g-samroberts in
[#20917](https://github.com/google-gemini/gemini-cli/pull/20917)
- fix(cli): pin clipboardy to ~5.2.x by @scidomino in
[#21009](https://github.com/google-gemini/gemini-cli/pull/21009)
- feat: Implement slash command handling in ACP for
`/memory`,`/init`,`/extensions` and `/restore` by @sripasg in
[#20528](https://github.com/google-gemini/gemini-cli/pull/20528)
- Docs/add hooks reference by @AadithyaAle in
[#20961](https://github.com/google-gemini/gemini-cli/pull/20961)
- feat(plan): add copy subcommand to plan (#20491) by @ruomengz in
[#20988](https://github.com/google-gemini/gemini-cli/pull/20988)
- fix(core): sanitize and length-check MCP tool qualified names by @abhipatel12
in [#20987](https://github.com/google-gemini/gemini-cli/pull/20987)
- Format the quota/limit style guide. by @g-samroberts in
[#21017](https://github.com/google-gemini/gemini-cli/pull/21017)
- fix(core): send shell output to model on cancel by @devr0306 in
[#20501](https://github.com/google-gemini/gemini-cli/pull/20501)
- remove hardcoded tiername when missing tier by @sehoon38 in
[#21022](https://github.com/google-gemini/gemini-cli/pull/21022)
- feat(acp): add set models interface by @skeshive in
[#20991](https://github.com/google-gemini/gemini-cli/pull/20991)
- fix(patch): cherry-pick 0659ad1 to release/v0.33.0-preview.0-pr-21042 to patch
version v0.33.0-preview.0 and create version 0.33.0-preview.1 by
@gemini-cli-robot in
[#21047](https://github.com/google-gemini/gemini-cli/pull/21047)
- fix(patch): cherry-pick 173376b to release/v0.33.0-preview.1-pr-21157 to patch
version v0.33.0-preview.1 and create version 0.33.0-preview.2 by
@gemini-cli-robot in
[#21300](https://github.com/google-gemini/gemini-cli/pull/21300)
- fix(patch): cherry-pick 0135b03 to release/v0.33.0-preview.2-pr-21171
[CONFLICTS] by @gemini-cli-robot in
[#21336](https://github.com/google-gemini/gemini-cli/pull/21336)
- fix(patch): cherry-pick 7ec477d to release/v0.33.0-preview.3-pr-21305 to patch
version v0.33.0-preview.3 and create version 0.33.0-preview.4 by
@gemini-cli-robot in
[#21349](https://github.com/google-gemini/gemini-cli/pull/21349)
- fix(patch): cherry-pick 931e668 to release/v0.33.0-preview.4-pr-21425
[CONFLICTS] by @gemini-cli-robot in
[#21478](https://github.com/google-gemini/gemini-cli/pull/21478)
- fix(patch): cherry-pick 7837194 to release/v0.33.0-preview.5-pr-21487 to patch
version v0.33.0-preview.5 and create version 0.33.0-preview.6 by
@gemini-cli-robot in
[#21720](https://github.com/google-gemini/gemini-cli/pull/21720)
- fix(patch): cherry-pick 4f4431e to release/v0.33.0-preview.7-pr-21750 to patch
version v0.33.0-preview.7 and create version 0.33.0-preview.8 by
@gemini-cli-robot in
[#21782](https://github.com/google-gemini/gemini-cli/pull/21782)
- fix(patch): cherry-pick 9a74271 to release/v0.33.0-preview.8-pr-21236
[CONFLICTS] by @gemini-cli-robot in
[#21788](https://github.com/google-gemini/gemini-cli/pull/21788)
- fix(patch): cherry-pick 936f624 to release/v0.33.0-preview.9-pr-21702 to patch
version v0.33.0-preview.9 and create version 0.33.0-preview.10 by
@gemini-cli-robot in
[#21800](https://github.com/google-gemini/gemini-cli/pull/21800)
- fix(patch): cherry-pick 35ee2a8 to release/v0.33.0-preview.10-pr-21713 by
@gemini-cli-robot in
[#21859](https://github.com/google-gemini/gemini-cli/pull/21859)
- fix(patch): cherry-pick 5dd2dab to release/v0.33.0-preview.11-pr-21871 by
@gemini-cli-robot in
[#21876](https://github.com/google-gemini/gemini-cli/pull/21876)
- fix(patch): cherry-pick e5615f4 to release/v0.33.0-preview.12-pr-21037 to
patch version v0.33.0-preview.12 and create version 0.33.0-preview.13 by
@gemini-cli-robot in
[#21922](https://github.com/google-gemini/gemini-cli/pull/21922)
- fix(patch): cherry-pick 1b69637 to release/v0.33.0-preview.13-pr-21467
[CONFLICTS] by @gemini-cli-robot in
[#21930](https://github.com/google-gemini/gemini-cli/pull/21930)
- fix(patch): cherry-pick 3ff68a9 to release/v0.33.0-preview.14-pr-21884
[CONFLICTS] by @gemini-cli-robot in
[#21952](https://github.com/google-gemini/gemini-cli/pull/21952)
[#20328](https://github.com/google-gemini/gemini-cli/pull/20328)
- feat(plan): support opening and modifying plan in external editor by @Adib234
in [#20348](https://github.com/google-gemini/gemini-cli/pull/20348)
- feat(cli): implement interactive shell autocompletion by @mrpmohiburrahman in
[#20082](https://github.com/google-gemini/gemini-cli/pull/20082)
- fix(core): allow /memory add to work in plan mode by @Jefftree in
[#20353](https://github.com/google-gemini/gemini-cli/pull/20353)
- feat(core): add HTTP 499 to retryable errors and map to RetryableQuotaError by
@bdmorgan in [#20432](https://github.com/google-gemini/gemini-cli/pull/20432)
- feat(core): Enable generalist agent by @joshualitt in
[#19665](https://github.com/google-gemini/gemini-cli/pull/19665)
- Updated tests in TableRenderer.test.tsx to use SVG snapshots by @devr0306 in
[#20450](https://github.com/google-gemini/gemini-cli/pull/20450)
- Refactor Github Action per b/485167538 by @google-admin in
[#19443](https://github.com/google-gemini/gemini-cli/pull/19443)
- fix(github): resolve actionlint and yamllint regressions from #19443 by @jerop
in [#20467](https://github.com/google-gemini/gemini-cli/pull/20467)
- fix: action var usage by @galz10 in
[#20492](https://github.com/google-gemini/gemini-cli/pull/20492)
- feat(core): improve A2A content extraction by @adamfweidman in
[#20487](https://github.com/google-gemini/gemini-cli/pull/20487)
- fix(cli): support quota error fallbacks for all authentication types by
@sehoon38 in [#20475](https://github.com/google-gemini/gemini-cli/pull/20475)
- fix(core): flush transcript for pure tool-call responses to ensure BeforeTool
hooks see complete state by @krishdef7 in
[#20419](https://github.com/google-gemini/gemini-cli/pull/20419)
- feat(plan): adapt planning workflow based on complexity of task by @jerop in
[#20465](https://github.com/google-gemini/gemini-cli/pull/20465)
- fix: prevent orphaned processes from consuming 100% CPU when terminal closes
by @yuvrajangadsingh in
[#16965](https://github.com/google-gemini/gemini-cli/pull/16965)
- feat(core): increase fetch timeout and fix [object Object] error
stringification by @bdmorgan in
[#20441](https://github.com/google-gemini/gemini-cli/pull/20441)
- [Gemma x Gemini CLI] Add an Experimental Gemma Router that uses a LiteRT-LM
shim into the Composite Model Classifier Strategy by @sidwan02 in
[#17231](https://github.com/google-gemini/gemini-cli/pull/17231)
- docs(plan): update documentation regarding supporting editing of plan files
during plan approval by @Adib234 in
[#20452](https://github.com/google-gemini/gemini-cli/pull/20452)
- test(cli): fix flaky ToolResultDisplay overflow test by @jwhelangoog in
[#20518](https://github.com/google-gemini/gemini-cli/pull/20518)
- ui(cli): reduce length of Ctrl+O hint by @jwhelangoog in
[#20490](https://github.com/google-gemini/gemini-cli/pull/20490)
- fix(ui): correct styled table width calculations by @devr0306 in
[#20042](https://github.com/google-gemini/gemini-cli/pull/20042)
- Avoid overaggressive unescaping by @scidomino in
[#20520](https://github.com/google-gemini/gemini-cli/pull/20520)
- feat(telemetry) Instrument traces with more attributes and make them available
to OTEL users by @heaventourist in
[#20237](https://github.com/google-gemini/gemini-cli/pull/20237)
- Add support for policy engine in extensions by @chrstnb in
[#20049](https://github.com/google-gemini/gemini-cli/pull/20049)
- Docs: Update to Terms of Service & FAQ by @jkcinouye in
[#20488](https://github.com/google-gemini/gemini-cli/pull/20488)
- Fix bottom border rendering for search and add a regression test. by @jacob314
in [#20517](https://github.com/google-gemini/gemini-cli/pull/20517)
- fix(core): apply retry logic to CodeAssistServer for all users by @bdmorgan in
[#20507](https://github.com/google-gemini/gemini-cli/pull/20507)
- Fix extension MCP server env var loading by @chrstnb in
[#20374](https://github.com/google-gemini/gemini-cli/pull/20374)
- feat(ui): add 'ctrl+o' hint to truncated content message by @jerop in
[#20529](https://github.com/google-gemini/gemini-cli/pull/20529)
- Fix flicker showing message to press ctrl-O again to collapse. by @jacob314 in
[#20414](https://github.com/google-gemini/gemini-cli/pull/20414)
- fix(cli): hide shortcuts hint while model is thinking or the user has typed a
prompt + add debounce to avoid flicker by @jacob314 in
[#19389](https://github.com/google-gemini/gemini-cli/pull/19389)
- feat(plan): update planning workflow to encourage multi-select with
descriptions of options by @Adib234 in
[#20491](https://github.com/google-gemini/gemini-cli/pull/20491)
- refactor(core,cli): useAlternateBuffer read from config by @psinha40898 in
[#20346](https://github.com/google-gemini/gemini-cli/pull/20346)
- fix(cli): ensure dialogs stay scrolled to bottom in alternate buffer mode by
@jacob314 in [#20527](https://github.com/google-gemini/gemini-cli/pull/20527)
- fix(core): revert auto-save of policies to user space by @Abhijit-2592 in
[#20531](https://github.com/google-gemini/gemini-cli/pull/20531)
- Demote unreliable test. by @gundermanc in
[#20571](https://github.com/google-gemini/gemini-cli/pull/20571)
- fix(core): handle optional response fields from code assist API by @sehoon38
in [#20345](https://github.com/google-gemini/gemini-cli/pull/20345)
- fix(cli): keep thought summary when loading phrases are off by @LyalinDotCom
in [#20497](https://github.com/google-gemini/gemini-cli/pull/20497)
- feat(cli): add temporary flag to disable workspace policies by @Abhijit-2592
in [#20523](https://github.com/google-gemini/gemini-cli/pull/20523)
- Disable expensive and scheduled workflows on personal forks by @dewitt in
[#20449](https://github.com/google-gemini/gemini-cli/pull/20449)
- Moved markdown parsing logic to a separate util file by @devr0306 in
[#20526](https://github.com/google-gemini/gemini-cli/pull/20526)
- fix(plan): prevent agent from using ask_user for shell command confirmation by
@Adib234 in [#20504](https://github.com/google-gemini/gemini-cli/pull/20504)
- fix(core): disable retries for code assist streaming requests by @sehoon38 in
[#20561](https://github.com/google-gemini/gemini-cli/pull/20561)
- feat(billing): implement G1 AI credits overage flow with billing telemetry by
@gsquared94 in
[#18590](https://github.com/google-gemini/gemini-cli/pull/18590)
- feat: better error messages by @gsquared94 in
[#20577](https://github.com/google-gemini/gemini-cli/pull/20577)
- fix(ui): persist expansion in AskUser dialog when navigating options by @jerop
in [#20559](https://github.com/google-gemini/gemini-cli/pull/20559)
- fix(cli): prevent sub-agent tool calls from leaking into UI by @abhipatel12 in
[#20580](https://github.com/google-gemini/gemini-cli/pull/20580)
- fix(cli): Shell autocomplete polish by @jacob314 in
[#20411](https://github.com/google-gemini/gemini-cli/pull/20411)
- Changelog for v0.31.0-preview.1 by @gemini-cli-robot in
[#20590](https://github.com/google-gemini/gemini-cli/pull/20590)
- Add slash command for promoting behavioral evals to CI blocking by @gundermanc
in [#20575](https://github.com/google-gemini/gemini-cli/pull/20575)
- Changelog for v0.30.1 by @gemini-cli-robot in
[#20589](https://github.com/google-gemini/gemini-cli/pull/20589)
- Add low/full CLI error verbosity mode for cleaner UI by @LyalinDotCom in
[#20399](https://github.com/google-gemini/gemini-cli/pull/20399)
- Disable Gemini PR reviews on draft PRs. by @gundermanc in
[#20362](https://github.com/google-gemini/gemini-cli/pull/20362)
- Docs: FAQ update by @jkcinouye in
[#20585](https://github.com/google-gemini/gemini-cli/pull/20585)
- fix(core): reduce intrusive MCP errors and deduplicate diagnostics by
@spencer426 in
[#20232](https://github.com/google-gemini/gemini-cli/pull/20232)
- docs: fix spelling typos in installation guide by @campox747 in
[#20579](https://github.com/google-gemini/gemini-cli/pull/20579)
- Promote stable tests to CI blocking. by @gundermanc in
[#20581](https://github.com/google-gemini/gemini-cli/pull/20581)
- feat(core): enable contiguous parallel admission for Kind.Agent tools by
@abhipatel12 in
[#20583](https://github.com/google-gemini/gemini-cli/pull/20583)
- Enforce import/no-duplicates as error by @Nixxx19 in
[#19797](https://github.com/google-gemini/gemini-cli/pull/19797)
- fix: merge duplicate imports in sdk and test-utils packages (1/4) by @Nixxx19
in [#19777](https://github.com/google-gemini/gemini-cli/pull/19777)
- fix: merge duplicate imports in a2a-server package (2/4) by @Nixxx19 in
[#19781](https://github.com/google-gemini/gemini-cli/pull/19781)
**Full Changelog**:
https://github.com/google-gemini/gemini-cli/compare/v0.32.1...v0.33.1
https://github.com/google-gemini/gemini-cli/compare/v0.31.0...v0.32.1
+182 -448
View File
@@ -1,6 +1,6 @@
# Preview release: v0.34.0-preview.1
# Preview release: v0.33.0-preview.13
Released: March 12, 2026
Released: March 10, 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).
@@ -13,459 +13,193 @@ npm install -g @google/gemini-cli@preview
## Highlights
- **Plan Mode Enabled by Default:** Plan Mode is now enabled out-of-the-box,
providing a structured planning workflow and keeping approved plans during
chat compression.
- **Sandboxing Enhancements:** Added experimental LXC container sandbox support
and native gVisor (`runsc`) sandboxing for improved security and isolation.
- **Tracker Visualization and Tools:** Introduced CRUD tools and visualization
for trackers, along with task tracker strategy improvements.
- **Browser Agent Improvements:** Enhanced the browser agent with progress
emission, a new automation overlay, and additional integration tests.
- **CLI and UI Updates:** Standardized semantic focus colors, polished shell
autocomplete rendering, unified keybinding infrastructure, and added custom
footer configuration options.
- **Plan Mode Enhancements**: Added support for annotating plans with feedback
for iteration, enabling built-in research subagents in plan mode, and a new
`copy` subcommand.
- **Agent and Skill Improvements**: Introduced the new `github-issue-creator`
skill, implemented HTTP authentication support for A2A remote agents, and
added support for authenticated A2A agent card discovery.
- **CLI UX/UI Updates**: Redesigned the header to be compact with an ASCII icon,
inverted the context window display to show usage, and directly indicate auth
required state for agents.
- **Core and ACP Enhancements**: Implemented slash command handling in ACP (for
`/memory`, `/init`, `/extensions`, and `/restore`), added a set models
interface to ACP, and centralized `read_file` limits while truncating large
MCP tool output.
## What's Changed
- fix(patch): cherry-pick 45faf4d to release/v0.34.0-preview.0-pr-22148
- fix(patch): cherry-pick e5615f4 to release/v0.33.0-preview.12-pr-21037 to
patch version v0.33.0-preview.12 and create version 0.33.0-preview.13 by
@gemini-cli-robot in
[#21922](https://github.com/google-gemini/gemini-cli/pull/21922)
- fix(patch): cherry-pick 7ec477d to release/v0.33.0-preview.3-pr-21305 to patch
version v0.33.0-preview.3 and create version 0.33.0-preview.4 by
@gemini-cli-robot in
[#21349](https://github.com/google-gemini/gemini-cli/pull/21349)
- fix(patch): cherry-pick 0135b03 to release/v0.33.0-preview.2-pr-21171
[CONFLICTS] by @gemini-cli-robot in
[#22174](https://github.com/google-gemini/gemini-cli/pull/22174)
- feat(cli): add chat resume footer on session quit by @lordshashank in
[#20667](https://github.com/google-gemini/gemini-cli/pull/20667)
- Support bold and other styles in svg snapshots by @jacob314 in
[#20937](https://github.com/google-gemini/gemini-cli/pull/20937)
- fix(core): increase A2A agent timeout to 30 minutes by @adamfweidman in
[#21028](https://github.com/google-gemini/gemini-cli/pull/21028)
- Cleanup old branches. by @jacob314 in
[#19354](https://github.com/google-gemini/gemini-cli/pull/19354)
- chore(release): bump version to 0.34.0-nightly.20260303.34f0c1538 by
[#21336](https://github.com/google-gemini/gemini-cli/pull/21336)
- fix(patch): cherry-pick 173376b to release/v0.33.0-preview.1-pr-21157 to patch
version v0.33.0-preview.1 and create version 0.33.0-preview.2 by
@gemini-cli-robot in
[#21034](https://github.com/google-gemini/gemini-cli/pull/21034)
- feat(ui): standardize semantic focus colors and enhance history visibility by
@keithguerin in
[#20745](https://github.com/google-gemini/gemini-cli/pull/20745)
- fix: merge duplicate imports in packages/core (3/4) by @Nixxx19 in
[#20928](https://github.com/google-gemini/gemini-cli/pull/20928)
- Add extra safety checks for proto pollution by @jacob314 in
[#20396](https://github.com/google-gemini/gemini-cli/pull/20396)
- feat(core): Add tracker CRUD tools & visualization by @anj-s in
[#19489](https://github.com/google-gemini/gemini-cli/pull/19489)
- Revert "fix(ui): persist expansion in AskUser dialog when navigating options"
by @jacob314 in
[#21042](https://github.com/google-gemini/gemini-cli/pull/21042)
- Changelog for v0.33.0-preview.0 by @gemini-cli-robot in
[#21030](https://github.com/google-gemini/gemini-cli/pull/21030)
- fix: model persistence for all scenarios by @sripasg in
[#21051](https://github.com/google-gemini/gemini-cli/pull/21051)
- chore/release: bump version to 0.34.0-nightly.20260304.28af4e127 by
[#21300](https://github.com/google-gemini/gemini-cli/pull/21300)
- fix(patch): cherry-pick 173376b to release/v0.33.0-preview.1-pr-21157 to patch
version v0.33.0-preview.1 and create version 0.33.0-preview.2 by
@gemini-cli-robot in
[#21054](https://github.com/google-gemini/gemini-cli/pull/21054)
- Consistently guard restarts against concurrent auto updates by @scidomino in
[#21016](https://github.com/google-gemini/gemini-cli/pull/21016)
- Defensive coding to reduce the risk of Maximum update depth errors by
@jacob314 in [#20940](https://github.com/google-gemini/gemini-cli/pull/20940)
- fix(cli): Polish shell autocomplete rendering to be a little more shell native
feeling. by @jacob314 in
[#20931](https://github.com/google-gemini/gemini-cli/pull/20931)
- Docs: Update plan mode docs by @jkcinouye in
[#19682](https://github.com/google-gemini/gemini-cli/pull/19682)
- fix(mcp): Notifications/tools/list_changed support not working by @jacob314 in
[#21050](https://github.com/google-gemini/gemini-cli/pull/21050)
- fix(cli): register extension lifecycle events in DebugProfiler by
@fayerman-source in
[#20101](https://github.com/google-gemini/gemini-cli/pull/20101)
- chore(dev): update vscode settings for typescriptreact by @rohit-4321 in
[#19907](https://github.com/google-gemini/gemini-cli/pull/19907)
- fix(cli): enable multi-arch docker builds for sandbox by @ru-aish in
[#19821](https://github.com/google-gemini/gemini-cli/pull/19821)
- Changelog for v0.32.0 by @gemini-cli-robot in
[#21033](https://github.com/google-gemini/gemini-cli/pull/21033)
- Changelog for v0.33.0-preview.1 by @gemini-cli-robot in
[#21058](https://github.com/google-gemini/gemini-cli/pull/21058)
- feat(core): improve @scripts/copy_files.js autocomplete to prioritize
filenames by @sehoon38 in
[#21064](https://github.com/google-gemini/gemini-cli/pull/21064)
- feat(sandbox): add experimental LXC container sandbox support by @h30s in
[#20735](https://github.com/google-gemini/gemini-cli/pull/20735)
- feat(evals): add overall pass rate row to eval nightly summary table by
@gundermanc in
[#20905](https://github.com/google-gemini/gemini-cli/pull/20905)
- feat(telemetry): include language in telemetry and fix accepted lines
computation by @gundermanc in
[#21126](https://github.com/google-gemini/gemini-cli/pull/21126)
- Changelog for v0.32.1 by @gemini-cli-robot in
[#21055](https://github.com/google-gemini/gemini-cli/pull/21055)
- feat(core): add robustness tests, logging, and metrics for CodeAssistServer
SSE parsing by @yunaseoul in
[#21013](https://github.com/google-gemini/gemini-cli/pull/21013)
- feat: add issue assignee workflow by @kartikangiras in
[#21003](https://github.com/google-gemini/gemini-cli/pull/21003)
- fix: improve error message when OAuth succeeds but project ID is required by
@Nixxx19 in [#21070](https://github.com/google-gemini/gemini-cli/pull/21070)
- feat(loop-reduction): implement iterative loop detection and model feedback by
[#21300](https://github.com/google-gemini/gemini-cli/pull/21300)
- fix(patch): cherry-pick 0659ad1 to release/v0.33.0-preview.0-pr-21042 to patch
version v0.33.0-preview.0 and create version 0.33.0-preview.1 by
@gemini-cli-robot in
[#21047](https://github.com/google-gemini/gemini-cli/pull/21047)
- Docs: Update model docs to remove Preview Features. by @jkcinouye in
[#20084](https://github.com/google-gemini/gemini-cli/pull/20084)
- docs: fix typo in installation documentation by @AdityaSharma-Git3207 in
[#20153](https://github.com/google-gemini/gemini-cli/pull/20153)
- docs: add Windows PowerShell equivalents for environments and scripting by
@scidomino in [#20333](https://github.com/google-gemini/gemini-cli/pull/20333)
- fix(core): parse raw ASCII buffer strings in Gaxios errors by @sehoon38 in
[#20626](https://github.com/google-gemini/gemini-cli/pull/20626)
- chore(release): bump version to 0.33.0-nightly.20260227.ba149afa0 by @galz10
in [#20637](https://github.com/google-gemini/gemini-cli/pull/20637)
- fix(github): use robot PAT for automated PRs to pass CLA check by @galz10 in
[#20641](https://github.com/google-gemini/gemini-cli/pull/20641)
- chore/release: bump version to 0.33.0-nightly.20260228.1ca5c05d0 by
@gemini-cli-robot in
[#20644](https://github.com/google-gemini/gemini-cli/pull/20644)
- Changelog for v0.31.0 by @gemini-cli-robot in
[#20634](https://github.com/google-gemini/gemini-cli/pull/20634)
- fix: use full paths for ACP diff payloads by @JagjeevanAK in
[#19539](https://github.com/google-gemini/gemini-cli/pull/19539)
- Changelog for v0.32.0-preview.0 by @gemini-cli-robot in
[#20627](https://github.com/google-gemini/gemini-cli/pull/20627)
- fix: acp/zed race condition between MCP initialisation and prompt by
@kartikangiras in
[#20205](https://github.com/google-gemini/gemini-cli/pull/20205)
- fix(cli): reset themeManager between tests to ensure isolation by
@NTaylorMullen in
[#20598](https://github.com/google-gemini/gemini-cli/pull/20598)
- refactor(core): Extract tool parameter names as constants by @SandyTao520 in
[#20460](https://github.com/google-gemini/gemini-cli/pull/20460)
- fix(cli): resolve autoThemeSwitching when background hasn't changed but theme
mismatches by @sehoon38 in
[#20706](https://github.com/google-gemini/gemini-cli/pull/20706)
- feat(skills): add github-issue-creator skill by @sehoon38 in
[#20709](https://github.com/google-gemini/gemini-cli/pull/20709)
- fix(cli): allow sub-agent confirmation requests in UI while preventing
background flicker by @abhipatel12 in
[#20722](https://github.com/google-gemini/gemini-cli/pull/20722)
- Merge User and Agent Card Descriptions #20849 by @adamfweidman in
[#20850](https://github.com/google-gemini/gemini-cli/pull/20850)
- fix(core): reduce LLM-based loop detection false positives by @SandyTao520 in
[#20701](https://github.com/google-gemini/gemini-cli/pull/20701)
- fix(plan): deflake plan mode integration tests by @Adib234 in
[#20477](https://github.com/google-gemini/gemini-cli/pull/20477)
- Add /unassign support by @scidomino in
[#20864](https://github.com/google-gemini/gemini-cli/pull/20864)
- feat(core): implement HTTP authentication support for A2A remote agents by
@SandyTao520 in
[#20510](https://github.com/google-gemini/gemini-cli/pull/20510)
- feat(core): centralize read_file limits and update gemini-3 description by
@aishaneeshah in
[#20763](https://github.com/google-gemini/gemini-cli/pull/20763)
- chore(github): require prompt approvers for agent prompt files by @gundermanc
in [#20896](https://github.com/google-gemini/gemini-cli/pull/20896)
- Docs: Create tools reference by @jkcinouye in
[#19470](https://github.com/google-gemini/gemini-cli/pull/19470)
- fix(core, a2a-server): prevent hang during OAuth in non-interactive sessions
by @spencer426 in
[#21045](https://github.com/google-gemini/gemini-cli/pull/21045)
- chore(cli): enable deprecated settings removal by default by @yashodipmore in
[#20682](https://github.com/google-gemini/gemini-cli/pull/20682)
- feat(core): Disable fast ack helper for hints. by @joshualitt in
[#21011](https://github.com/google-gemini/gemini-cli/pull/21011)
- fix(ui): suppress redundant failure note when tool error note is shown by
@NTaylorMullen in
[#21078](https://github.com/google-gemini/gemini-cli/pull/21078)
- docs: document planning workflows with Conductor example by @jerop in
[#21166](https://github.com/google-gemini/gemini-cli/pull/21166)
- feat(release): ship esbuild bundle in npm package by @genneth in
[#19171](https://github.com/google-gemini/gemini-cli/pull/19171)
- fix(extensions): preserve symlinks in extension source path while enforcing
folder trust by @galz10 in
[#20867](https://github.com/google-gemini/gemini-cli/pull/20867)
- fix(cli): defer tool exclusions to policy engine in non-interactive mode by
@EricRahm in [#20639](https://github.com/google-gemini/gemini-cli/pull/20639)
- fix(ui): removed double padding on rendered content by @devr0306 in
[#21029](https://github.com/google-gemini/gemini-cli/pull/21029)
- fix(core): truncate excessively long lines in grep search output by
@gundermanc in
[#21147](https://github.com/google-gemini/gemini-cli/pull/21147)
- feat: add custom footer configuration via `/footer` by @jackwotherspoon in
[#19001](https://github.com/google-gemini/gemini-cli/pull/19001)
- perf(core): fix OOM crash in long-running sessions by @WizardsForgeGames in
[#19608](https://github.com/google-gemini/gemini-cli/pull/19608)
- refactor(cli): categorize built-in themes into dark/ and light/ directories by
@JayadityaGit in
[#18634](https://github.com/google-gemini/gemini-cli/pull/18634)
- fix(core): explicitly allow codebase_investigator and cli_help in read-only
mode by @Adib234 in
[#21157](https://github.com/google-gemini/gemini-cli/pull/21157)
- test: add browser agent integration tests by @kunal-10-cloud in
[#21151](https://github.com/google-gemini/gemini-cli/pull/21151)
- fix(cli): fix enabling kitty codes on Windows Terminal by @scidomino in
[#21136](https://github.com/google-gemini/gemini-cli/pull/21136)
- refactor(core): extract shared OAuth flow primitives from MCPOAuthProvider by
@SandyTao520 in
[#20895](https://github.com/google-gemini/gemini-cli/pull/20895)
- fix(ui): add partial output to cancelled shell UI by @devr0306 in
[#21178](https://github.com/google-gemini/gemini-cli/pull/21178)
- fix(cli): replace hardcoded keybinding strings with dynamic formatters by
@scidomino in [#21159](https://github.com/google-gemini/gemini-cli/pull/21159)
- DOCS: Update quota and pricing page by @g-samroberts in
[#21194](https://github.com/google-gemini/gemini-cli/pull/21194)
- feat(telemetry): implement Clearcut logging for startup statistics by
@yunaseoul in [#21172](https://github.com/google-gemini/gemini-cli/pull/21172)
- feat(triage): add area/documentation to issue triage by @g-samroberts in
[#21222](https://github.com/google-gemini/gemini-cli/pull/21222)
- Fix so shell calls are formatted by @jacob314 in
[#21237](https://github.com/google-gemini/gemini-cli/pull/21237)
- feat(cli): add native gVisor (runsc) sandboxing support by @Zheyuan-Lin in
[#21062](https://github.com/google-gemini/gemini-cli/pull/21062)
- docs: use absolute paths for internal links in plan-mode.md by @jerop in
[#21299](https://github.com/google-gemini/gemini-cli/pull/21299)
- fix(core): prevent unhandled AbortError crash during stream loop detection by
@7hokerz in [#21123](https://github.com/google-gemini/gemini-cli/pull/21123)
- fix:reorder env var redaction checks to scan values first by @kartikangiras in
[#21059](https://github.com/google-gemini/gemini-cli/pull/21059)
- fix(acp): rename --experimental-acp to --acp & remove Zed-specific refrences
[#20619](https://github.com/google-gemini/gemini-cli/pull/20619)
- Do not block CI on evals by @gundermanc in
[#20870](https://github.com/google-gemini/gemini-cli/pull/20870)
- document node limitation for shift+tab by @scidomino in
[#20877](https://github.com/google-gemini/gemini-cli/pull/20877)
- Add install as an option when extension is selected. by @DavidAPierce in
[#20358](https://github.com/google-gemini/gemini-cli/pull/20358)
- Update CODEOWNERS for README.md reviewers by @g-samroberts in
[#20860](https://github.com/google-gemini/gemini-cli/pull/20860)
- feat(core): truncate large MCP tool output by @SandyTao520 in
[#19365](https://github.com/google-gemini/gemini-cli/pull/19365)
- Subagent activity UX. by @gundermanc in
[#17570](https://github.com/google-gemini/gemini-cli/pull/17570)
- style(cli) : Dialog pattern for /hooks Command by @AbdulTawabJuly in
[#17930](https://github.com/google-gemini/gemini-cli/pull/17930)
- feat: redesign header to be compact with ASCII icon by @keithguerin in
[#18713](https://github.com/google-gemini/gemini-cli/pull/18713)
- fix(core): ensure subagents use qualified MCP tool names by @abhipatel12 in
[#20801](https://github.com/google-gemini/gemini-cli/pull/20801)
- feat(core): support authenticated A2A agent card discovery by @SandyTao520 in
[#20622](https://github.com/google-gemini/gemini-cli/pull/20622)
- refactor(cli): fully remove React anti patterns, improve type safety and fix
UX oversights in SettingsDialog.tsx by @psinha40898 in
[#18963](https://github.com/google-gemini/gemini-cli/pull/18963)
- Adding MCPOAuthProvider implementing the MCPSDK OAuthClientProvider by
@Nayana-Parameswarappa in
[#20121](https://github.com/google-gemini/gemini-cli/pull/20121)
- feat(core): add tool name validation in TOML policy files by @allenhutchison
in [#19281](https://github.com/google-gemini/gemini-cli/pull/19281)
- docs: fix broken markdown links in main README.md by @Hamdanbinhashim in
[#20300](https://github.com/google-gemini/gemini-cli/pull/20300)
- refactor(core): replace manual syncPlanModeTools with declarative policy rules
by @jerop in [#20596](https://github.com/google-gemini/gemini-cli/pull/20596)
- fix(core): increase default headers timeout to 5 minutes by @gundermanc in
[#20890](https://github.com/google-gemini/gemini-cli/pull/20890)
- feat(admin): enable 30 day default retention for chat history & remove warning
by @skeshive in
[#21171](https://github.com/google-gemini/gemini-cli/pull/21171)
- feat(core): fallback to 2.5 models with no access for toolcalls by @sehoon38
in [#21283](https://github.com/google-gemini/gemini-cli/pull/21283)
- test(core): improve testing for API request/response parsing by @sehoon38 in
[#21227](https://github.com/google-gemini/gemini-cli/pull/21227)
- docs(links): update docs-writer skill and fix broken link by @g-samroberts in
[#21314](https://github.com/google-gemini/gemini-cli/pull/21314)
- Fix code colorizer ansi escape bug. by @jacob314 in
[#21321](https://github.com/google-gemini/gemini-cli/pull/21321)
- remove wildcard behavior on keybindings by @scidomino in
[#21315](https://github.com/google-gemini/gemini-cli/pull/21315)
- feat(acp): Add support for AI Gateway auth by @skeshive in
[#21305](https://github.com/google-gemini/gemini-cli/pull/21305)
- fix(theme): improve theme color contrast for macOS Terminal.app by @clocky in
[#21175](https://github.com/google-gemini/gemini-cli/pull/21175)
- feat (core): Implement tracker related SI changes by @anj-s in
[#19964](https://github.com/google-gemini/gemini-cli/pull/19964)
- Changelog for v0.33.0-preview.2 by @gemini-cli-robot in
[#21333](https://github.com/google-gemini/gemini-cli/pull/21333)
- Changelog for v0.33.0-preview.3 by @gemini-cli-robot in
[#21347](https://github.com/google-gemini/gemini-cli/pull/21347)
- docs: format release times as HH:MM UTC by @pavan-sh in
[#20726](https://github.com/google-gemini/gemini-cli/pull/20726)
- fix(cli): implement --all flag for extensions uninstall by @sehoon38 in
[#21319](https://github.com/google-gemini/gemini-cli/pull/21319)
- docs: fix incorrect relative links to command reference by @kanywst in
[#20964](https://github.com/google-gemini/gemini-cli/pull/20964)
- documentiong ensures ripgrep by @Jatin24062005 in
[#21298](https://github.com/google-gemini/gemini-cli/pull/21298)
- fix(core): handle AbortError thrown during processTurn by @MumuTW in
[#21296](https://github.com/google-gemini/gemini-cli/pull/21296)
- docs(cli): clarify ! command output visibility in shell commands tutorial by
@MohammedADev in
[#21041](https://github.com/google-gemini/gemini-cli/pull/21041)
- fix: logic for task tracker strategy and remove tracker tools by @anj-s in
[#21355](https://github.com/google-gemini/gemini-cli/pull/21355)
- fix(partUtils): display media type and size for inline data parts by @Aboudjem
in [#21358](https://github.com/google-gemini/gemini-cli/pull/21358)
- Fix(accessibility): add screen reader support to RewindViewer by @Famous077 in
[#20750](https://github.com/google-gemini/gemini-cli/pull/20750)
- fix(hooks): propagate stopHookActive in AfterAgent retry path (#20426) by
@Aarchi-07 in [#20439](https://github.com/google-gemini/gemini-cli/pull/20439)
- fix(core): deduplicate GEMINI.md files by device/inode on case-insensitive
filesystems (#19904) by @Nixxx19 in
[#19915](https://github.com/google-gemini/gemini-cli/pull/19915)
- feat(core): add concurrency safety guidance for subagent delegation (#17753)
by @abhipatel12 in
[#21278](https://github.com/google-gemini/gemini-cli/pull/21278)
- feat(ui): dynamically generate all keybinding hints by @scidomino in
[#21346](https://github.com/google-gemini/gemini-cli/pull/21346)
- feat(core): implement unified KeychainService and migrate token storage by
@ehedlund in [#21344](https://github.com/google-gemini/gemini-cli/pull/21344)
- fix(cli): gracefully handle --resume when no sessions exist by @SandyTao520 in
[#21429](https://github.com/google-gemini/gemini-cli/pull/21429)
- fix(plan): keep approved plan during chat compression by @ruomengz in
[#21284](https://github.com/google-gemini/gemini-cli/pull/21284)
- feat(core): implement generic CacheService and optimize setupUser by @sehoon38
in [#21374](https://github.com/google-gemini/gemini-cli/pull/21374)
- Update quota and pricing documentation with subscription tiers by @srithreepo
in [#21351](https://github.com/google-gemini/gemini-cli/pull/21351)
- fix(core): append correct OTLP paths for HTTP exporters by
@sebastien-prudhomme in
[#16836](https://github.com/google-gemini/gemini-cli/pull/16836)
- Changelog for v0.33.0-preview.4 by @gemini-cli-robot in
[#21354](https://github.com/google-gemini/gemini-cli/pull/21354)
- feat(cli): implement dot-prefixing for slash command conflicts by @ehedlund in
[#20979](https://github.com/google-gemini/gemini-cli/pull/20979)
- refactor(core): standardize MCP tool naming to mcp\_ FQN format by
@abhipatel12 in
[#21425](https://github.com/google-gemini/gemini-cli/pull/21425)
- feat(cli): hide gemma settings from display and mark as experimental by
@abhipatel12 in
[#21471](https://github.com/google-gemini/gemini-cli/pull/21471)
- feat(skills): refine string-reviewer guidelines and description by @clocky in
[#20368](https://github.com/google-gemini/gemini-cli/pull/20368)
- fix(core): whitelist TERM and COLORTERM in environment sanitization by
@deadsmash07 in
[#20514](https://github.com/google-gemini/gemini-cli/pull/20514)
- fix(billing): fix overage strategy lifecycle and settings integration by
@gsquared94 in
[#21236](https://github.com/google-gemini/gemini-cli/pull/21236)
- fix: expand paste placeholders in TextInput on submit by @Jefftree in
[#19946](https://github.com/google-gemini/gemini-cli/pull/19946)
- fix(core): add in-memory cache to ChatRecordingService to prevent OOM by
@SandyTao520 in
[#21502](https://github.com/google-gemini/gemini-cli/pull/21502)
- feat(cli): overhaul thinking UI by @keithguerin in
[#18725](https://github.com/google-gemini/gemini-cli/pull/18725)
- fix(ui): unify Ctrl+O expansion hint experience across buffer modes by
@jwhelangoog in
[#21474](https://github.com/google-gemini/gemini-cli/pull/21474)
- fix(cli): correct shell height reporting by @jacob314 in
[#21492](https://github.com/google-gemini/gemini-cli/pull/21492)
- Make test suite pass when the GEMINI_SYSTEM_MD env variable or
GEMINI_WRITE_SYSTEM_MD variable happens to be set locally/ by @jacob314 in
[#21480](https://github.com/google-gemini/gemini-cli/pull/21480)
- Disallow underspecified types by @gundermanc in
[#21485](https://github.com/google-gemini/gemini-cli/pull/21485)
- refactor(cli): standardize on 'reload' verb for all components by @keithguerin
in [#20654](https://github.com/google-gemini/gemini-cli/pull/20654)
- feat(cli): Invert quota language to 'percent used' by @keithguerin in
[#20100](https://github.com/google-gemini/gemini-cli/pull/20100)
- Docs: Add documentation for notifications (experimental)(macOS) by @jkcinouye
in [#21163](https://github.com/google-gemini/gemini-cli/pull/21163)
- Code review comments as a pr by @jacob314 in
[#21209](https://github.com/google-gemini/gemini-cli/pull/21209)
- feat(cli): unify /chat and /resume command UX by @LyalinDotCom in
[#20256](https://github.com/google-gemini/gemini-cli/pull/20256)
- docs: fix typo 'allowslisted' -> 'allowlisted' in mcp-server.md by
@Gyanranjan-Priyam in
[#21665](https://github.com/google-gemini/gemini-cli/pull/21665)
- fix(core): display actual graph output in tracker_visualize tool by @anj-s in
[#21455](https://github.com/google-gemini/gemini-cli/pull/21455)
- fix(core): sanitize SSE-corrupted JSON and domain strings in error
classification by @gsquared94 in
[#21702](https://github.com/google-gemini/gemini-cli/pull/21702)
- Docs: Make documentation links relative by @diodesign in
[#21490](https://github.com/google-gemini/gemini-cli/pull/21490)
- feat(cli): expose /tools desc as explicit subcommand for discoverability by
@aworki in [#21241](https://github.com/google-gemini/gemini-cli/pull/21241)
- feat(cli): add /compact alias for /compress command by @jackwotherspoon in
[#21711](https://github.com/google-gemini/gemini-cli/pull/21711)
- feat(plan): enable Plan Mode by default by @jerop in
[#21713](https://github.com/google-gemini/gemini-cli/pull/21713)
- feat(core): Introduce `AgentLoopContext`. by @joshualitt in
[#21198](https://github.com/google-gemini/gemini-cli/pull/21198)
- fix(core): resolve symlinks for non-existent paths during validation by
@Adib234 in [#21487](https://github.com/google-gemini/gemini-cli/pull/21487)
- docs: document tool exclusion from memory via deny policy by @Abhijit-2592 in
[#21428](https://github.com/google-gemini/gemini-cli/pull/21428)
- perf(core): cache loadApiKey to reduce redundant keychain access by @sehoon38
in [#21520](https://github.com/google-gemini/gemini-cli/pull/21520)
- feat(cli): implement /upgrade command by @sehoon38 in
[#21511](https://github.com/google-gemini/gemini-cli/pull/21511)
- Feat/browser agent progress emission by @kunal-10-cloud in
[#21218](https://github.com/google-gemini/gemini-cli/pull/21218)
- fix(settings): display objects as JSON instead of [object Object] by
@Zheyuan-Lin in
[#21458](https://github.com/google-gemini/gemini-cli/pull/21458)
- Unmarshall update by @DavidAPierce in
[#21721](https://github.com/google-gemini/gemini-cli/pull/21721)
- Update mcp's list function to check for disablement. by @DavidAPierce in
[#21148](https://github.com/google-gemini/gemini-cli/pull/21148)
- robustness(core): static checks to validate history is immutable by @jacob314
in [#21228](https://github.com/google-gemini/gemini-cli/pull/21228)
- refactor(cli): better react patterns for BaseSettingsDialog by @psinha40898 in
[#21206](https://github.com/google-gemini/gemini-cli/pull/21206)
- feat(security): implement robust IP validation and safeFetch foundation by
@alisa-alisa in
[#21401](https://github.com/google-gemini/gemini-cli/pull/21401)
- feat(core): improve subagent result display by @joshualitt in
[#20378](https://github.com/google-gemini/gemini-cli/pull/20378)
- docs: fix broken markdown syntax and anchor links in /tools by @campox747 in
[#20902](https://github.com/google-gemini/gemini-cli/pull/20902)
- feat(policy): support subagent-specific policies in TOML by @akh64bit in
[#21431](https://github.com/google-gemini/gemini-cli/pull/21431)
- Add script to speed up reviewing PRs adding a worktree. by @jacob314 in
[#21748](https://github.com/google-gemini/gemini-cli/pull/21748)
- fix(core): prevent infinite recursion in symlink resolution by @Adib234 in
[#21750](https://github.com/google-gemini/gemini-cli/pull/21750)
- fix(docs): fix headless mode docs by @ame2en in
[#21287](https://github.com/google-gemini/gemini-cli/pull/21287)
- feat/redesign header compact by @jacob314 in
[#20922](https://github.com/google-gemini/gemini-cli/pull/20922)
- refactor: migrate to useKeyMatchers hook by @scidomino in
[#21753](https://github.com/google-gemini/gemini-cli/pull/21753)
- perf(cli): cache loadSettings to reduce redundant disk I/O at startup by
@sehoon38 in [#21521](https://github.com/google-gemini/gemini-cli/pull/21521)
- fix(core): resolve Windows line ending and path separation bugs across CLI by
@muhammadusman586 in
[#21068](https://github.com/google-gemini/gemini-cli/pull/21068)
- docs: fix heading formatting in commands.md and phrasing in tools-api.md by
@campox747 in [#20679](https://github.com/google-gemini/gemini-cli/pull/20679)
- refactor(ui): unify keybinding infrastructure and support string
initialization by @scidomino in
[#21776](https://github.com/google-gemini/gemini-cli/pull/21776)
- Add support for updating extension sources and names by @chrstnb in
[#21715](https://github.com/google-gemini/gemini-cli/pull/21715)
- fix(core): handle GUI editor non-zero exit codes gracefully by @reyyanxahmed
in [#20376](https://github.com/google-gemini/gemini-cli/pull/20376)
- fix(core): destroy PTY on kill() and exception to prevent fd leak by @nbardy
in [#21693](https://github.com/google-gemini/gemini-cli/pull/21693)
- fix(docs): update theme screenshots and add missing themes by @ashmod in
[#20689](https://github.com/google-gemini/gemini-cli/pull/20689)
- refactor(cli): rename 'return' key to 'enter' internally by @scidomino in
[#21796](https://github.com/google-gemini/gemini-cli/pull/21796)
- build(release): restrict npm bundling to non-stable tags by @sehoon38 in
[#21821](https://github.com/google-gemini/gemini-cli/pull/21821)
- fix(core): override toolRegistry property for sub-agent schedulers by
@gsquared94 in
[#21766](https://github.com/google-gemini/gemini-cli/pull/21766)
- fix(cli): make footer items equally spaced by @jacob314 in
[#21843](https://github.com/google-gemini/gemini-cli/pull/21843)
- docs: clarify global policy rules application in plan mode by @jerop in
[#21864](https://github.com/google-gemini/gemini-cli/pull/21864)
- fix(core): ensure correct flash model steering in plan mode implementation
phase by @jerop in
[#21871](https://github.com/google-gemini/gemini-cli/pull/21871)
- fix(core): update @a2a-js/sdk to 0.3.11 by @adamfweidman in
[#21875](https://github.com/google-gemini/gemini-cli/pull/21875)
- refactor(core): improve API response error logging when retry by @yunaseoul in
[#21784](https://github.com/google-gemini/gemini-cli/pull/21784)
- fix(ui): handle headless execution in credits and upgrade dialogs by
@gsquared94 in
[#21850](https://github.com/google-gemini/gemini-cli/pull/21850)
- fix(core): treat retryable errors with >5 min delay as terminal quota errors
by @gsquared94 in
[#21881](https://github.com/google-gemini/gemini-cli/pull/21881)
- feat(telemetry): add specific PR, issue, and custom tracking IDs for GitHub
Actions by @cocosheng-g in
[#21129](https://github.com/google-gemini/gemini-cli/pull/21129)
- feat(core): add OAuth2 Authorization Code auth provider for A2A agents by
@SandyTao520 in
[#21496](https://github.com/google-gemini/gemini-cli/pull/21496)
- feat(cli): give visibility to /tools list command in the TUI and follow the
subcommand pattern of other commands by @JayadityaGit in
[#21213](https://github.com/google-gemini/gemini-cli/pull/21213)
- Handle dirty worktrees better and warn about running scripts/review.sh on
untrusted code. by @jacob314 in
[#21791](https://github.com/google-gemini/gemini-cli/pull/21791)
- feat(policy): support auto-add to policy by default and scoped persistence by
@spencer426 in
[#20361](https://github.com/google-gemini/gemini-cli/pull/20361)
- fix(core): handle AbortError when ESC cancels tool execution by @PrasannaPal21
in [#20863](https://github.com/google-gemini/gemini-cli/pull/20863)
- fix(release): Improve Patch Release Workflow Comments: Clearer Approval
Guidance by @jerop in
[#21894](https://github.com/google-gemini/gemini-cli/pull/21894)
- docs: clarify telemetry setup and comprehensive data map by @jerop in
[#21879](https://github.com/google-gemini/gemini-cli/pull/21879)
- feat(core): add per-model token usage to stream-json output by @yongruilin in
[#21839](https://github.com/google-gemini/gemini-cli/pull/21839)
- docs: remove experimental badge from plan mode in sidebar by @jerop in
[#21906](https://github.com/google-gemini/gemini-cli/pull/21906)
- fix(cli): prevent race condition in loop detection retry by @skyvanguard in
[#17916](https://github.com/google-gemini/gemini-cli/pull/17916)
- Add behavioral evals for tracker by @anj-s in
[#20069](https://github.com/google-gemini/gemini-cli/pull/20069)
- fix(auth): update terminology to 'sign in' and 'sign out' by @clocky in
[#20892](https://github.com/google-gemini/gemini-cli/pull/20892)
- docs(mcp): standardize mcp tool fqn documentation by @abhipatel12 in
[#21664](https://github.com/google-gemini/gemini-cli/pull/21664)
- fix(ui): prevent empty tool-group border stubs after filtering by @Aaxhirrr in
[#21852](https://github.com/google-gemini/gemini-cli/pull/21852)
- make command names consistent by @scidomino in
[#21907](https://github.com/google-gemini/gemini-cli/pull/21907)
- refactor: remove agent_card_requires_auth config flag by @adamfweidman in
[#21914](https://github.com/google-gemini/gemini-cli/pull/21914)
- feat(a2a): implement standardized normalization and streaming reassembly by
@alisa-alisa in
[#21402](https://github.com/google-gemini/gemini-cli/pull/21402)
- feat(cli): enable skill activation via slash commands by @NTaylorMullen in
[#21758](https://github.com/google-gemini/gemini-cli/pull/21758)
- docs(cli): mention per-model token usage in stream-json result event by
@yongruilin in
[#21908](https://github.com/google-gemini/gemini-cli/pull/21908)
- fix(plan): prevent plan truncation in approval dialog by supporting
unconstrained heights by @Adib234 in
[#21037](https://github.com/google-gemini/gemini-cli/pull/21037)
- feat(a2a): switch from callback-based to event-driven tool scheduler by
@cocosheng-g in
[#21467](https://github.com/google-gemini/gemini-cli/pull/21467)
- feat(voice): implement speech-friendly response formatter by @Solventerritory
in [#20989](https://github.com/google-gemini/gemini-cli/pull/20989)
- feat: add pulsating blue border automation overlay to browser agent by
@kunal-10-cloud in
[#21173](https://github.com/google-gemini/gemini-cli/pull/21173)
- Add extensionRegistryURI setting to change where the registry is read from by
@kevinjwang1 in
[#20463](https://github.com/google-gemini/gemini-cli/pull/20463)
- fix: patch gaxios v7 Array.toString() stream corruption by @gsquared94 in
[#21884](https://github.com/google-gemini/gemini-cli/pull/21884)
- fix: prevent hangs in non-interactive mode and improve agent guidance by
@cocosheng-g in
[#20893](https://github.com/google-gemini/gemini-cli/pull/20893)
- Add ExtensionDetails dialog and support install by @chrstnb in
[#20845](https://github.com/google-gemini/gemini-cli/pull/20845)
- chore/release: bump version to 0.34.0-nightly.20260310.4653b126f by
@gemini-cli-robot in
[#21816](https://github.com/google-gemini/gemini-cli/pull/21816)
- Changelog for v0.33.0-preview.13 by @gemini-cli-robot in
[#21927](https://github.com/google-gemini/gemini-cli/pull/21927)
- fix(cli): stabilize prompt layout to prevent jumping when typing by
@NTaylorMullen in
[#21081](https://github.com/google-gemini/gemini-cli/pull/21081)
- fix: preserve prompt text when cancelling streaming by @Nixxx19 in
[#21103](https://github.com/google-gemini/gemini-cli/pull/21103)
- fix: robust UX for remote agent errors by @Shyam-Raghuwanshi in
[#20307](https://github.com/google-gemini/gemini-cli/pull/20307)
- feat: implement background process logging and cleanup by @galz10 in
[#21189](https://github.com/google-gemini/gemini-cli/pull/21189)
- Changelog for v0.33.0-preview.14 by @gemini-cli-robot in
[#21938](https://github.com/google-gemini/gemini-cli/pull/21938)
[#20853](https://github.com/google-gemini/gemini-cli/pull/20853)
- feat(plan): support annotating plans with feedback for iteration by @Adib234
in [#20876](https://github.com/google-gemini/gemini-cli/pull/20876)
- Add some dos and don'ts to behavioral evals README. by @gundermanc in
[#20629](https://github.com/google-gemini/gemini-cli/pull/20629)
- fix(core): skip telemetry logging for AbortError exceptions by @yunaseoul in
[#19477](https://github.com/google-gemini/gemini-cli/pull/19477)
- fix(core): restrict "System: Please continue" invalid stream retry to Gemini 2
models by @SandyTao520 in
[#20897](https://github.com/google-gemini/gemini-cli/pull/20897)
- ci(evals): only run evals in CI if prompts or tools changed by @gundermanc in
[#20898](https://github.com/google-gemini/gemini-cli/pull/20898)
- Build binary by @aswinashok44 in
[#18933](https://github.com/google-gemini/gemini-cli/pull/18933)
- Code review fixes as a pr by @jacob314 in
[#20612](https://github.com/google-gemini/gemini-cli/pull/20612)
- fix(ci): handle empty APP_ID in stale PR closer by @bdmorgan in
[#20919](https://github.com/google-gemini/gemini-cli/pull/20919)
- feat(cli): invert context window display to show usage by @keithguerin in
[#20071](https://github.com/google-gemini/gemini-cli/pull/20071)
- fix(plan): clean up session directories and plans on deletion by @jerop in
[#20914](https://github.com/google-gemini/gemini-cli/pull/20914)
- fix(core): enforce optionality for API response fields in code_assist by
@sehoon38 in [#20714](https://github.com/google-gemini/gemini-cli/pull/20714)
- feat(extensions): add support for plan directory in extension manifest by
@mahimashanware in
[#20354](https://github.com/google-gemini/gemini-cli/pull/20354)
- feat(plan): enable built-in research subagents in plan mode by @Adib234 in
[#20972](https://github.com/google-gemini/gemini-cli/pull/20972)
- feat(agents): directly indicate auth required state by @adamfweidman in
[#20986](https://github.com/google-gemini/gemini-cli/pull/20986)
- fix(cli): wait for background auto-update before relaunching by @scidomino in
[#20904](https://github.com/google-gemini/gemini-cli/pull/20904)
- fix: pre-load @scripts/copy_files.js references from external editor prompts
by @kartikangiras in
[#20963](https://github.com/google-gemini/gemini-cli/pull/20963)
- feat(evals): add behavioral evals for ask_user tool by @Adib234 in
[#20620](https://github.com/google-gemini/gemini-cli/pull/20620)
- refactor common settings logic for skills,agents by @ishaanxgupta in
[#17490](https://github.com/google-gemini/gemini-cli/pull/17490)
- Update docs-writer skill with new resource by @g-samroberts in
[#20917](https://github.com/google-gemini/gemini-cli/pull/20917)
- fix(cli): pin clipboardy to ~5.2.x by @scidomino in
[#21009](https://github.com/google-gemini/gemini-cli/pull/21009)
- feat: Implement slash command handling in ACP for
`/memory`,`/init`,`/extensions` and `/restore` by @sripasg in
[#20528](https://github.com/google-gemini/gemini-cli/pull/20528)
- Docs/add hooks reference by @AadithyaAle in
[#20961](https://github.com/google-gemini/gemini-cli/pull/20961)
- feat(plan): add copy subcommand to plan (#20491) by @ruomengz in
[#20988](https://github.com/google-gemini/gemini-cli/pull/20988)
- fix(core): sanitize and length-check MCP tool qualified names by @abhipatel12
in [#20987](https://github.com/google-gemini/gemini-cli/pull/20987)
- Format the quota/limit style guide. by @g-samroberts in
[#21017](https://github.com/google-gemini/gemini-cli/pull/21017)
- fix(core): send shell output to model on cancel by @devr0306 in
[#20501](https://github.com/google-gemini/gemini-cli/pull/20501)
- remove hardcoded tiername when missing tier by @sehoon38 in
[#21022](https://github.com/google-gemini/gemini-cli/pull/21022)
- feat(acp): add set models interface by @skeshive in
[#20991](https://github.com/google-gemini/gemini-cli/pull/20991)
**Full Changelog**:
https://github.com/google-gemini/gemini-cli/compare/v0.33.0-preview.15...v0.34.0-preview.1
https://github.com/google-gemini/gemini-cli/compare/v0.32.0-preview.0...v0.33.0-preview.13
+1 -18
View File
@@ -26,20 +26,6 @@ policies.
the CLI will use an available fallback model for the current turn or the
remainder of the session.
### Local Model Routing (Experimental)
Gemini CLI supports using a local model for routing decisions. When configured,
Gemini CLI will use a locally-running **Gemma** model to make routing decisions
(instead of sending routing decisions to a hosted model). This feature can help
reduce costs associated with hosted model usage while offering similar routing
decision latency and quality.
In order to use this feature, the local Gemma model **must** be served behind a
Gemini API and accessible via HTTP at an endpoint configured in `settings.json`.
For more details on how to configure local model routing, see
[Local Model Routing](../core/local-model-routing.md).
### Model selection precedence
The model used by Gemini CLI is determined by the following order of precedence:
@@ -52,8 +38,5 @@ The model used by Gemini CLI is determined by the following order of precedence:
3. **`model.name` in `settings.json`:** If neither of the above are set, the
model specified in the `model.name` property of your `settings.json` file
will be used.
4. **Local model (experimental):** If the Gemma local model router is enabled
in your `settings.json` file, the CLI will use the local Gemma model
(instead of Gemini models) to route the request to an appropriate model.
5. **Default model:** If none of the above are set, the default model will be
4. **Default model:** If none of the above are set, the default model will be
used. The default model is `auto`
-79
View File
@@ -1,79 +0,0 @@
# Model steering (experimental)
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`.
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
stays on the right track.
## Enabling model steering
Model steering is an experimental feature and is disabled by default. You can
enable it using the `/settings` command or by updating your `settings.json`
file.
1. Type `/settings` in the Gemini CLI.
2. Search for **Model Steering**.
3. Set the value to **true**.
Alternatively, add the following to your `settings.json`:
```json
{
"experimental": {
"modelSteering": true
}
}
```
## Using model steering
When model steering is enabled, Gemini CLI treats any text you type while the
agent is working as a steering hint.
1. Start a task (for example, "Refactor the database service").
2. While the agent is working (the spinner is visible), type your feedback in
the input box.
3. Press **Enter**.
Gemini CLI acknowledges your hint with a brief message and injects it directly
into the model's context for the very next turn. The model then re-evaluates its
current plan and adjusts its actions accordingly.
### Common use cases
You can use steering hints to guide the model in several ways:
- **Correcting a path:** "Actually, the utilities are in `src/common/utils`."
- **Skipping a step:** "Skip the unit tests for now and just focus on the
implementation."
- **Adding context:** "The `User` type is defined in `packages/core/types.ts`."
- **Redirecting the effort:** "Stop searching the codebase and start drafting
the plan now."
- **Handling ambiguity:** "Use the existing `Logger` class instead of creating a
new one."
## How it works
When you submit a steering hint, Gemini CLI performs the following actions:
1. **Immediate acknowledgment:** It uses a small, fast model to generate a
one-sentence acknowledgment so you know your hint was received.
2. **Context injection:** It prepends an internal instruction to your hint that
tells the main agent to:
- Re-evaluate the active plan.
- Classify the update (for example, as a new task or extra context).
- Apply minimal-diff changes to affected tasks.
3. **Real-time update:** The hint is delivered to the agent at the beginning of
its next turn, ensuring the most immediate course correction possible.
## Next steps
- Tackle complex tasks with [Plan Mode](./plan-mode.md).
- Build custom [Agent Skills](./skills.md).
+14 -99
View File
@@ -61,44 +61,20 @@ Gemini CLI takes action.
[`ask_user`](../tools/ask-user.md). Provide your preferences to help guide
the design.
3. **Review the plan:** Once Gemini CLI has a proposed strategy, it creates a
detailed implementation plan as a Markdown file in your plans directory.
- **View:** You can open and read this file to understand the proposed
changes.
- **Edit:** Press `Ctrl+X` to open the plan directly in your configured
external editor.
detailed implementation plan as a Markdown file in your plans directory. You
can open and read this file to understand the proposed changes.
4. **Approve or iterate:** Gemini CLI will present the finalized plan for your
approval.
- **Approve:** If you're satisfied with the plan, approve it to start the
implementation immediately: **Yes, automatically accept edits** or **Yes,
manually accept edits**.
- **Iterate:** If the plan needs adjustments, provide feedback in the input
box or [edit the plan file directly](#collaborative-plan-editing). Gemini
CLI will refine the strategy and update the plan.
- **Iterate:** If the plan needs adjustments, provide feedback. Gemini CLI
will refine the strategy and update the plan.
- **Cancel:** You can cancel your plan with `Esc`.
For more complex or specialized planning tasks, you can
[customize the planning workflow with skills](#custom-planning-with-skills).
### Collaborative plan editing
You can collaborate with Gemini CLI by making direct changes or leaving comments
in the implementation plan. This is often faster and more precise than
describing complex changes in natural language.
1. **Open the plan:** Press `Ctrl+X` when Gemini CLI presents a plan for
review.
2. **Edit or comment:** The plan opens in your configured external editor (for
example, VS Code or Vim). You can:
- **Modify steps:** Directly reorder, delete, or rewrite implementation
steps.
- **Leave comments:** Add inline questions or feedback (for example, "Wait,
shouldn't we use the existing `Logger` class here?").
3. **Save and close:** Save your changes and close the editor.
4. **Review and refine:** Gemini CLI automatically detects the changes, reviews
your comments, and adjusts the implementation strategy. It then presents the
refined plan for your final approval.
## How to exit Plan Mode
You can exit Plan Mode at any time, whether you have finalized a plan or want to
@@ -109,6 +85,16 @@ switch back to another mode.
- **Keyboard shortcut:** Press `Shift+Tab` to cycle to the desired mode.
- **Natural language:** Ask Gemini CLI to "exit plan mode" or "stop planning."
## Customization and best practices
Plan Mode is secure by default, but you can adapt it to fit your specific
workflows. You can customize how Gemini CLI plans by using skills, adjusting
safety policies, or changing where plans are stored.
## Commands
- **`/plan copy`**: Copy the currently approved plan to your clipboard.
## Tool Restrictions
Plan Mode enforces strict safety policies to prevent accidental changes.
@@ -136,12 +122,6 @@ These are the only allowed tools:
- **Skills:** [`activate_skill`](../cli/skills.md) (allows loading specialized
instructions and resources in a read-only manner)
## Customization and best practices
Plan Mode is secure by default, but you can adapt it to fit your specific
workflows. You can customize how Gemini CLI plans by using skills, adjusting
safety policies, changing where plans are stored, or adding hooks.
### Custom planning with skills
You can use [Agent Skills](../cli/skills.md) to customize how Gemini CLI
@@ -290,71 +270,6 @@ modes = ["plan"]
argsPattern = "\"file_path\":\"[^\"]+[\\\\/]+\\.gemini[\\\\/]+plans[\\\\/]+[\\w-]+\\.md\""
```
### Using hooks with Plan Mode
You can use the [hook system](../hooks/writing-hooks.md) to automate parts of
the planning workflow or enforce additional checks when Gemini CLI transitions
into or out of Plan Mode.
Hooks such as `BeforeTool` or `AfterTool` can be configured to intercept the
`enter_plan_mode` and `exit_plan_mode` tool calls.
> [!WARNING] When hooks are triggered by **tool executions**, they do **not**
> run when you manually toggle Plan Mode using the `/plan` command or the
> `Shift+Tab` keyboard shortcut. If you need hooks to execute on mode changes,
> ensure the transition is initiated by the agent (e.g., by asking "start a plan
> for...").
#### Example: Archive approved plans to GCS (`AfterTool`)
If your organizational policy requires a record of all execution plans, you can
use an `AfterTool` hook to securely copy the plan artifact to Google Cloud
Storage whenever Gemini CLI exits Plan Mode to start the implementation.
**`.gemini/hooks/archive-plan.sh`:**
```bash
#!/usr/bin/env bash
# Extract the plan path from the tool input JSON
plan_path=$(jq -r '.tool_input.plan_path // empty')
if [ -f "$plan_path" ]; then
# Generate a unique filename using a timestamp
filename="$(date +%s)_$(basename "$plan_path")"
# Upload the plan to GCS in the background so it doesn't block the CLI
gsutil cp "$plan_path" "gs://my-audit-bucket/gemini-plans/$filename" > /dev/null 2>&1 &
fi
# AfterTool hooks should generally allow the flow to continue
echo '{"decision": "allow"}'
```
To register this `AfterTool` hook, add it to your `settings.json`:
```json
{
"hooks": {
"AfterTool": [
{
"matcher": "exit_plan_mode",
"hooks": [
{
"name": "archive-plan",
"type": "command",
"command": "./.gemini/hooks/archive-plan.sh"
}
]
}
]
}
}
```
## Commands
- **`/plan copy`**: Copy the currently approved plan to your clipboard.
## Planning workflows
Plan Mode provides building blocks for structured research and design. These are
+13 -15
View File
@@ -22,19 +22,18 @@ they appear in the UI.
### General
| UI Label | Setting | Description | Default |
| ----------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| Vim Mode | `general.vimMode` | Enable Vim keybindings | `false` |
| Default Approval Mode | `general.defaultApprovalMode` | The default approval mode for tool execution. 'default' prompts for approval, 'auto_edit' auto-approves edit tools, and 'plan' is read-only mode. YOLO mode (auto-approve all actions) can only be enabled via command line (--yolo or --approval-mode=yolo). | `"default"` |
| Enable Auto Update | `general.enableAutoUpdate` | Enable automatic updates. | `true` |
| Enable Notifications | `general.enableNotifications` | Enable run-event notifications for action-required prompts and session completion. Currently macOS only. | `false` |
| Plan Directory | `general.plan.directory` | The directory where planning artifacts are stored. If not specified, defaults to the system temporary directory. | `undefined` |
| Plan Model Routing | `general.plan.modelRouting` | Automatically switch between Pro and Flash models based on Plan Mode status. Uses Pro for the planning phase and Flash for the implementation phase. | `true` |
| Retry Fetch Errors | `general.retryFetchErrors` | Retry on "exception TypeError: fetch failed sending request" errors. | `true` |
| Max Chat Model Attempts | `general.maxAttempts` | Maximum number of attempts for requests to the main chat model. Cannot exceed 10. | `10` |
| Debug Keystroke Logging | `general.debugKeystrokeLogging` | Enable debug logging of keystrokes to the console. | `false` |
| Enable Session Cleanup | `general.sessionRetention.enabled` | Enable automatic session cleanup | `true` |
| Keep chat history | `general.sessionRetention.maxAge` | Automatically delete chats older than this time period (e.g., "30d", "7d", "24h", "1w") | `"30d"` |
| UI Label | Setting | Description | Default |
| ----------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------- |
| Vim Mode | `general.vimMode` | Enable Vim keybindings | `false` |
| Default Approval Mode | `general.defaultApprovalMode` | The default approval mode for tool execution. 'default' prompts for approval, 'auto_edit' auto-approves edit tools, and 'plan' is read-only mode. 'yolo' is not supported yet. | `"default"` |
| Enable Auto Update | `general.enableAutoUpdate` | Enable automatic updates. | `true` |
| Enable Notifications | `general.enableNotifications` | Enable run-event notifications for action-required prompts and session completion. Currently macOS only. | `false` |
| Plan Directory | `general.plan.directory` | The directory where planning artifacts are stored. If not specified, defaults to the system temporary directory. | `undefined` |
| Plan Model Routing | `general.plan.modelRouting` | Automatically switch between Pro and Flash models based on Plan Mode status. Uses Pro for the planning phase and Flash for the implementation phase. | `true` |
| Max Chat Model Attempts | `general.maxAttempts` | Maximum number of attempts for requests to the main chat model. Cannot exceed 10. | `10` |
| Debug Keystroke Logging | `general.debugKeystrokeLogging` | Enable debug logging of keystrokes to the console. | `false` |
| Enable Session Cleanup | `general.sessionRetention.enabled` | Enable automatic session cleanup | `true` |
| Keep chat history | `general.sessionRetention.maxAge` | Automatically delete chats older than this time period (e.g., "30d", "7d", "24h", "1w") | `"30d"` |
### Output
@@ -55,11 +54,10 @@ they appear in the UI.
| Show Home Directory Warning | `ui.showHomeDirectoryWarning` | Show a warning when running Gemini CLI in the home directory. | `true` |
| Show Compatibility Warnings | `ui.showCompatibilityWarnings` | Show warnings about terminal or OS compatibility issues. | `true` |
| Hide Tips | `ui.hideTips` | Hide helpful tips in the UI | `false` |
| Escape Pasted @ Symbols | `ui.escapePastedAtSymbols` | When enabled, @ symbols in pasted text are escaped to prevent unintended @path expansion. | `false` |
| Show Shortcuts Hint | `ui.showShortcutsHint` | Show the "? for shortcuts" hint above the input. | `true` |
| Hide Banner | `ui.hideBanner` | Hide the application banner | `false` |
| Hide Context Summary | `ui.hideContextSummary` | Hide the context summary (GEMINI.md, MCP servers) above the input. | `false` |
| Hide CWD | `ui.footer.hideCWD` | Hide the current working directory in the footer. | `false` |
| Hide Workspace Path | `ui.footer.hideCWD` | Hide the workspace path in the footer. | `false` |
| Hide Sandbox Status | `ui.footer.hideSandboxStatus` | Hide the sandbox status indicator in the footer. | `false` |
| Hide Model Info | `ui.footer.hideModelInfo` | Hide the model name and context usage in the footer. | `false` |
| Hide Context Window Percentage | `ui.footer.hideContextPercentage` | Hides the context window usage percentage. | `true` |
-45
View File
@@ -45,7 +45,6 @@ Environment variables can override these settings.
| `logPrompts` | `GEMINI_TELEMETRY_LOG_PROMPTS` | Include prompts in telemetry logs | `true`/`false` | `true` |
| `useCollector` | `GEMINI_TELEMETRY_USE_COLLECTOR` | Use external OTLP collector (advanced) | `true`/`false` | `false` |
| `useCliAuth` | `GEMINI_TELEMETRY_USE_CLI_AUTH` | Use CLI credentials for telemetry (GCP target only) | `true`/`false` | `false` |
| - | `GEMINI_CLI_SURFACE` | Optional custom label for traffic reporting | string | - |
**Note on boolean environment variables:** For boolean settings like `enabled`,
setting the environment variable to `true` or `1` enables the feature.
@@ -217,50 +216,6 @@ recommend using file-based output for local development.
For advanced local telemetry setups (such as Jaeger or Genkit), see the
[Local development guide](../local-development.md#viewing-traces).
## Client identification
Gemini CLI includes identifiers in its `User-Agent` header to help you
differentiate and report on API traffic from different environments (for
example, identifying calls from Gemini Code Assist versus a standard terminal).
### Automatic identification
Most integrated environments are identified automatically without additional
configuration. The identifier is included as a prefix to the `User-Agent` and as
a "surface" tag in the parenthetical metadata.
| Environment | User-Agent Prefix | Surface Tag |
| :---------------------------------- | :--------------------------- | :---------- |
| **Gemini Code Assist (Agent Mode)** | `GeminiCLI-a2a-server` | `vscode` |
| **Zed (via ACP)** | `GeminiCLI-acp-zed` | `zed` |
| **XCode (via ACP)** | `GeminiCLI-acp-xcode` | `xcode` |
| **IntelliJ IDEA (via ACP)** | `GeminiCLI-acp-intellijidea` | `jetbrains` |
| **Standard Terminal** | `GeminiCLI` | `terminal` |
**Example User-Agent:**
`GeminiCLI-a2a-server/0.34.0/gemini-pro (linux; x64; vscode)`
### Custom identification
You can provide a custom identifier for your own scripts or automation by
setting the `GEMINI_CLI_SURFACE` environment variable. This is useful for
tracking specific internal tools or distribution channels in your GCP logs.
**macOS/Linux**
```bash
export GEMINI_CLI_SURFACE="my-custom-tool"
```
**Windows (PowerShell)**
```powershell
$env:GEMINI_CLI_SURFACE="my-custom-tool"
```
When set, the value appears at the end of the `User-Agent` parenthetical:
`GeminiCLI/0.34.0/gemini-pro (linux; x64; my-custom-tool)`
## Logs, metrics, and traces
This section describes the structure of logs, metrics, and traces generated by
-89
View File
@@ -1,89 +0,0 @@
# Use Plan Mode with model steering for complex tasks
Architecting a complex solution requires precision. By combining Plan Mode's
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`.
## Prerequisites
- Gemini CLI installed and authenticated.
- [Plan Mode](../plan-mode.md) enabled in your settings.
- [Model steering](../model-steering.md) enabled in your settings.
## Why combine Plan Mode and model steering?
[Plan Mode](../plan-mode.md) typically follows a linear path: research, propose,
and draft. Adding model steering lets you:
1. **Direct the research:** Correct the agent if it's looking in the wrong
directory or missing a key dependency.
2. **Iterate mid-draft:** Suggest a different architectural pattern while the
agent is still writing the plan.
3. **Speed up the loop:** Avoid waiting for a full research turn to finish
before providing critical context.
## Step 1: Start a complex task
Enter Plan Mode and start a task that requires research.
**Prompt:** `/plan I want to implement a new notification service using Redis.`
Gemini CLI enters Plan Mode and starts researching your existing codebase to
identify where the new service should live.
## Step 2: Steer the research phase
As you see the agent calling tools like `list_directory` or `grep_search`, you
might realize it's missing the relevant context.
**Action:** While the spinner is active, type your hint:
`"Don't forget to check packages/common/queues for the existing Redis config."`
**Result:** Gemini CLI acknowledges your hint and immediately incorporates it
into its research. You'll see it start exploring the directory you suggested in
its very next turn.
## Step 3: Refine the design mid-turn
After research, the agent starts drafting the implementation plan. If you notice
it's proposing a design that doesn't align with your goals, steer it.
**Action:** Type:
`"Actually, let's use a Publisher/Subscriber pattern instead of a simple queue for this service."`
**Result:** The agent stops drafting the current version of the plan,
re-evaluates the design based on your feedback, and starts a new draft that uses
the Pub/Sub pattern.
## Step 4: Approve and implement
Once the agent has used your hints to craft the perfect plan, review the final
`.md` file.
**Action:** Type: `"Looks perfect. Let's start the implementation."`
Gemini CLI exits Plan Mode and transitions to the implementation phase. Because
the plan was refined in real-time with your feedback, the agent can now execute
each step with higher confidence and fewer errors.
## Tips for effective steering
- **Be specific:** Instead of "do it differently," try "use the existing
`Logger` class in `src/utils`."
- **Steer early:** Providing feedback during the research phase is more
efficient than waiting for the final plan to be drafted.
- **Use for context:** Steering is a great way to provide knowledge that might
not be obvious from reading the code (e.g., "We are planning to deprecate this
module next month").
## Next steps
- Explore [Agent Skills](../skills.md) to add specialized expertise to your
planning turns.
- See the [Model steering reference](../model-steering.md) for technical
details.
-2
View File
@@ -15,8 +15,6 @@ requests sent from `packages/cli`. For a general overview of Gemini CLI, see the
modular GEMINI.md import feature using @file.md syntax.
- **[Policy Engine](../reference/policy-engine.md):** Use the Policy Engine for
fine-grained control over tool execution.
- **[Local Model Routing (experimental)](./local-model-routing.md):** Learn how
to enable use of a local Gemma model for model routing decisions.
## Role of the core
-193
View File
@@ -1,193 +0,0 @@
# Local Model Routing (experimental)
Gemini CLI supports using a local model for
[routing decisions](../cli/model-routing.md). When configured, Gemini CLI will
use a locally-running **Gemma** model to make routing decisions (instead of
sending routing decisions to a hosted model).
This feature can help reduce costs associated with hosted model usage while
offering similar routing decision latency and quality.
> **Note: Local model routing is currently an experimental feature.**
## Setup
Using a Gemma model for routing decisions requires that an implementation of a
Gemma model be running locally on your machine, served behind an HTTP endpoint
and accessed via the Gemini API.
To serve the Gemma model, follow these steps:
### Download the LiteRT-LM runtime
The [LiteRT-LM](https://github.com/google-ai-edge/LiteRT-LM) runtime offers
pre-built binaries for locally-serving models. Download the binary appropriate
for your system.
#### Windows
1. Download
[lit.windows_x86_64.exe](https://github.com/google-ai-edge/LiteRT-LM/releases/download/v0.9.0-alpha03/lit.windows_x86_64.exe).
2. Using GPU on Windows requires the DirectXShaderCompiler. Download the
[dxc zip from the latest release](https://github.com/microsoft/DirectXShaderCompiler/releases/download/v1.8.2505.1/dxc_2025_07_14.zip).
Unzip the archive and from the architecture-appropriate `bin\` directory, and
copy the `dxil.dll` and `dxcompiler.dll` into the same location as you saved
`lit.windows_x86_64.exe`.
3. (Optional) Test starting the runtime:
`.\lit.windows_x86_64.exe serve --verbose`
#### Linux
1. Download
[lit.linux_x86_64](https://github.com/google-ai-edge/LiteRT-LM/releases/download/v0.9.0-alpha03/lit.linux_x86_64).
2. Ensure the binary is executable: `chmod a+x lit.linux_x86_64`
3. (Optional) Test starting the runtime: `./lit.linux_x86_64 serve --verbose`
#### MacOS
1. Download
[lit-macos-arm64](https://github.com/google-ai-edge/LiteRT-LM/releases/download/v0.9.0-alpha03/lit.macos_arm64).
2. Ensure the binary is executable: `chmod a+x lit.macos_arm64`
3. (Optional) Test starting the runtime: `./lit.macos_arm64 serve --verbose`
> **Note**: MacOS can be configured to only allows binaries from "App Store &
> Known Developers". If you encounter an error message when attempting to run
> the binary, you will need to allow the application. One option is to visit
> `System Settings -> Privacy & Security`, scroll to `Security`, and click
> `"Allow Anyway"` for `"lit.macos_arm64"`. Another option is to run
> `xattr -d com.apple.quarantine lit.macos_arm64` from the commandline.
### Download the Gemma Model
Before using Gemma, you will need to download the model (and agree to the Terms
of Service).
This can be done via the LiteRT-LM runtime.
#### Windows
```bash
$ .\lit.windows_x86_64.exe pull gemma3-1b-gpu-custom
[Legal] The model you are about to download is governed by
the Gemma Terms of Use and Prohibited Use Policy. Please review these terms and ensure you agree before continuing.
Full Terms: https://ai.google.dev/gemma/terms
Prohibited Use Policy: https://ai.google.dev/gemma/prohibited_use_policy
Do you accept these terms? (Y/N): Y
Terms accepted.
Downloading model 'gemma3-1b-gpu-custom' ...
Downloading... 968.6 MB
Download complete.
```
#### Linux
```bash
$ ./lit.linux_x86_64 pull gemma3-1b-gpu-custom
[Legal] The model you are about to download is governed by
the Gemma Terms of Use and Prohibited Use Policy. Please review these terms and ensure you agree before continuing.
Full Terms: https://ai.google.dev/gemma/terms
Prohibited Use Policy: https://ai.google.dev/gemma/prohibited_use_policy
Do you accept these terms? (Y/N): Y
Terms accepted.
Downloading model 'gemma3-1b-gpu-custom' ...
Downloading... 968.6 MB
Download complete.
```
#### MacOS
```bash
$ ./lit.lit.macos_arm64 pull gemma3-1b-gpu-custom
[Legal] The model you are about to download is governed by
the Gemma Terms of Use and Prohibited Use Policy. Please review these terms and ensure you agree before continuing.
Full Terms: https://ai.google.dev/gemma/terms
Prohibited Use Policy: https://ai.google.dev/gemma/prohibited_use_policy
Do you accept these terms? (Y/N): Y
Terms accepted.
Downloading model 'gemma3-1b-gpu-custom' ...
Downloading... 968.6 MB
Download complete.
```
### Start LiteRT-LM Runtime
Using the command appropriate to your system, start the LiteRT-LM runtime.
Configure the port that you want to use for your Gemma model. For the purposes
of this document, we will use port `9379`.
Example command for MacOS: `./lit.macos_arm64 serve --port=9379 --verbose`
### (Optional) Verify Model Serving
Send a quick prompt to the model via HTTP to validate successful model serving.
This will cause the runtime to download the model and run it once.
You should see a short joke in the server output as an indicator of success.
#### Windows
```
# Run this in PowerShell to send a request to the server
$uri = "http://localhost:9379/v1beta/models/gemma3-1b-gpu-custom:generateContent"
$body = @{contents = @( @{
role = "user"
parts = @( @{ text = "Tell me a joke." } )
})} | ConvertTo-Json -Depth 10
Invoke-RestMethod -Uri $uri -Method Post -Body $body -ContentType "application/json"
```
#### Linux/MacOS
```bash
$ curl "http://localhost:9379/v1beta/models/gemma3-1b-gpu-custom:generateContent" \
-H 'Content-Type: application/json' \
-X POST \
-d '{"contents":[{"role":"user","parts":[{"text":"Tell me a joke."}]}]}'
```
## Configuration
To use a local Gemma model for routing, you must explicitly enable it in your
`settings.json`:
```json
{
"experimental": {
"gemmaModelRouter": {
"enabled": true,
"classifier": {
"host": "http://localhost:9379",
"model": "gemma3-1b-gpu-custom"
}
}
}
}
```
> Use the port you started your LiteRT-LM runtime on in the setup steps.
### Configuration schema
| Field | Type | Required | Description |
| :----------------- | :------ | :------- | :----------------------------------------------------------------------------------------- |
| `enabled` | boolean | Yes | Must be `true` to enable the feature. |
| `classifier` | object | Yes | The configuration for the local model endpoint. It includes the host and model specifiers. |
| `classifier.host` | string | Yes | The URL to the local model server. Should be `http://localhost:<port>`. |
| `classifier.model` | string | Yes | The model name to use for decisions. Must be `"gemma3-1b-gpu-custom"`. |
> **Note: You will need to restart after configuration changes for local model
> routing to take effect.**
+4 -4
View File
@@ -115,7 +115,7 @@ Slash commands provide meta-level control over the CLI itself.
- On macOS, it requires `pbcopy`, and on Windows, it requires `clip`. These
tools are typically pre-installed on their respective systems.
### `/directory` (or `/dir`)
### `/workspace` (or `/directory`, `/dir`)
- **Description:** Manage workspace directories for multi-directory support.
- **Sub-commands:**
@@ -123,13 +123,13 @@ Slash commands provide meta-level control over the CLI itself.
- **Description:** Add a directory to the workspace. The path can be
absolute or relative to the current working directory. Moreover, the
reference from home directory is supported as well.
- **Usage:** `/directory add <path1>,<path2>`
- **Usage:** `/workspace add <path1>,<path2>`
- **Note:** Disabled in restrictive sandbox profiles. If you're using that,
use `--include-directories` when starting the session instead.
- **`show`**:
- **Description:** Display all directories added by `/directory add` and
- **Description:** Display all directories added by `/workspace add` and
`--include-directories`.
- **Usage:** `/directory show`
- **Usage:** `/workspace show`
### `/docs`
+4 -169
View File
@@ -92,13 +92,6 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `[]`
- **Requires restart:** Yes
#### `adminPolicyPaths`
- **`adminPolicyPaths`** (array):
- **Description:** Additional admin policy files or directories to load.
- **Default:** `[]`
- **Requires restart:** Yes
#### `general`
- **`general.preferredEditor`** (string):
@@ -112,8 +105,7 @@ their corresponding top-level category object in your `settings.json` file.
- **`general.defaultApprovalMode`** (enum):
- **Description:** The default approval mode for tool execution. 'default'
prompts for approval, 'auto_edit' auto-approves edit tools, and 'plan' is
read-only mode. YOLO mode (auto-approve all actions) can only be enabled via
command line (--yolo or --approval-mode=yolo).
read-only mode. 'yolo' is not supported yet.
- **Default:** `"default"`
- **Values:** `"default"`, `"auto_edit"`, `"plan"`
@@ -154,7 +146,7 @@ their corresponding top-level category object in your `settings.json` file.
- **`general.retryFetchErrors`** (boolean):
- **Description:** Retry on "exception TypeError: fetch failed sending
request" errors.
- **Default:** `true`
- **Default:** `false`
- **`general.maxAttempts`** (number):
- **Description:** Maximum number of attempts for requests to the main chat
@@ -245,11 +237,6 @@ their corresponding top-level category object in your `settings.json` file.
- **Description:** Hide helpful tips in the UI
- **Default:** `false`
- **`ui.escapePastedAtSymbols`** (boolean):
- **Description:** When enabled, @ symbols in pasted text are escaped to
prevent unintended @path expansion.
- **Default:** `false`
- **`ui.showShortcutsHint`** (boolean):
- **Description:** Show the "? for shortcuts" hint above the input.
- **Default:** `true`
@@ -274,7 +261,7 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `true`
- **`ui.footer.hideCWD`** (boolean):
- **Description:** Hide the current working directory in the footer.
- **Description:** Hide the workspace path in the footer.
- **Default:** `false`
- **`ui.footer.hideSandboxStatus`** (boolean):
@@ -677,141 +664,6 @@ their corresponding top-level category object in your `settings.json` file.
used.
- **Default:** `[]`
- **`modelConfigs.modelDefinitions`** (object):
- **Description:** Registry of model metadata, including tier, family, and
features.
- **Default:**
```json
{
"gemini-3.1-pro-preview": {
"tier": "pro",
"family": "gemini-3",
"isPreview": true,
"dialogLocation": "manual",
"features": {
"thinking": true,
"multimodalToolUse": true
}
},
"gemini-3.1-pro-preview-customtools": {
"tier": "pro",
"family": "gemini-3",
"isPreview": true,
"features": {
"thinking": true,
"multimodalToolUse": true
}
},
"gemini-3-pro-preview": {
"tier": "pro",
"family": "gemini-3",
"isPreview": true,
"dialogLocation": "manual",
"features": {
"thinking": true,
"multimodalToolUse": true
}
},
"gemini-3-flash-preview": {
"tier": "flash",
"family": "gemini-3",
"isPreview": true,
"dialogLocation": "manual",
"features": {
"thinking": false,
"multimodalToolUse": true
}
},
"gemini-2.5-pro": {
"tier": "pro",
"family": "gemini-2.5",
"isPreview": false,
"dialogLocation": "manual",
"features": {
"thinking": false,
"multimodalToolUse": false
}
},
"gemini-2.5-flash": {
"tier": "flash",
"family": "gemini-2.5",
"isPreview": false,
"dialogLocation": "manual",
"features": {
"thinking": false,
"multimodalToolUse": false
}
},
"gemini-2.5-flash-lite": {
"tier": "flash-lite",
"family": "gemini-2.5",
"isPreview": false,
"dialogLocation": "manual",
"features": {
"thinking": false,
"multimodalToolUse": false
}
},
"auto": {
"tier": "auto",
"isPreview": true,
"features": {
"thinking": true,
"multimodalToolUse": false
}
},
"pro": {
"tier": "pro",
"isPreview": false,
"features": {
"thinking": true,
"multimodalToolUse": false
}
},
"flash": {
"tier": "flash",
"isPreview": false,
"features": {
"thinking": false,
"multimodalToolUse": false
}
},
"flash-lite": {
"tier": "flash-lite",
"isPreview": false,
"features": {
"thinking": false,
"multimodalToolUse": false
}
},
"auto-gemini-3": {
"displayName": "Auto (Gemini 3)",
"tier": "auto",
"isPreview": true,
"dialogLocation": "main",
"dialogDescription": "Let Gemini CLI decide the best model for the task: gemini-3.1-pro, gemini-3-flash",
"features": {
"thinking": true,
"multimodalToolUse": false
}
},
"auto-gemini-2.5": {
"displayName": "Auto (Gemini 2.5)",
"tier": "auto",
"isPreview": false,
"dialogLocation": "main",
"dialogDescription": "Let Gemini CLI decide the best model for the task: gemini-2.5-pro, gemini-2.5-flash",
"features": {
"thinking": false,
"multimodalToolUse": false
}
}
}
```
- **Requires restart:** Yes
#### `agents`
- **`agents.overrides`** (object):
@@ -841,10 +693,6 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `undefined`
- **Requires restart:** Yes
- **`agents.browser.disableUserInput`** (boolean):
- **Description:** Disable user input on browser window during automation.
- **Default:** `true`
#### `context`
- **`context.fileName`** (string | string[]):
@@ -907,7 +755,7 @@ their corresponding top-level category object in your `settings.json` file.
#### `tools`
- **`tools.sandbox`** (string):
- **`tools.sandbox`** (boolean | string):
- **Description:** 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").
@@ -1203,12 +1051,6 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `false`
- **Requires restart:** Yes
- **`experimental.dynamicModelConfiguration`** (boolean):
- **Description:** Enable dynamic model configuration (definitions,
resolutions, and chains) via settings.
- **Default:** `false`
- **Requires restart:** Yes
- **`experimental.gemmaModelRouter.enabled`** (boolean):
- **Description:** Enable the Gemma Model Router (experimental). Requires a
local endpoint serving Gemma via the Gemini API using LiteRT-LM shim.
@@ -1534,13 +1376,6 @@ the `advanced.excludedEnvVars` setting in your `settings.json` file.
- Useful for shared compute environments or keeping CLI state isolated.
- Example: `export GEMINI_CLI_HOME="/path/to/user/config"` (Windows
PowerShell: `$env:GEMINI_CLI_HOME="C:\path\to\user\config"`)
- **`GEMINI_CLI_SURFACE`**:
- Specifies a custom label to include in the `User-Agent` header for API
traffic reporting.
- This is useful for tracking specific internal tools or distribution
channels.
- Example: `export GEMINI_CLI_SURFACE="my-custom-tool"` (Windows PowerShell:
`$env:GEMINI_CLI_SURFACE="my-custom-tool"`)
- **`GOOGLE_API_KEY`**:
- Your Google Cloud API key.
- Required for using Vertex AI in express mode.
+90 -168
View File
@@ -8,201 +8,126 @@ available combinations.
#### Basic Controls
| Command | Action | Keys |
| --------------- | --------------------------------------------------------------- | ------------------- |
| `basic.confirm` | Confirm the current selection or choice. | `Enter` |
| `basic.cancel` | Dismiss dialogs or cancel the current focus. | `Esc`<br />`Ctrl+[` |
| `basic.quit` | Cancel the current request or quit the CLI when input is empty. | `Ctrl+C` |
| `basic.exit` | Exit the CLI when the input buffer is empty. | `Ctrl+D` |
| Action | Keys |
| --------------------------------------------------------------- | ------------------- |
| Confirm the current selection or choice. | `Enter` |
| Dismiss dialogs or cancel the current focus. | `Esc`<br />`Ctrl+[` |
| Cancel the current request or quit the CLI when input is empty. | `Ctrl+C` |
| Exit the CLI when the input buffer is empty. | `Ctrl+D` |
#### Cursor Movement
| Command | Action | Keys |
| ------------------ | ------------------------------------------- | ------------------------------------------ |
| `cursor.home` | Move the cursor to the start of the line. | `Ctrl+A`<br />`Home` |
| `cursor.end` | Move the cursor to the end of the line. | `Ctrl+E`<br />`End` |
| `cursor.up` | Move the cursor up one line. | `Up` |
| `cursor.down` | Move the cursor down one line. | `Down` |
| `cursor.left` | Move the cursor one character to the left. | `Left` |
| `cursor.right` | Move the cursor one character to the right. | `Right`<br />`Ctrl+F` |
| `cursor.wordLeft` | Move the cursor one word to the left. | `Ctrl+Left`<br />`Alt+Left`<br />`Alt+B` |
| `cursor.wordRight` | Move the cursor one word to the right. | `Ctrl+Right`<br />`Alt+Right`<br />`Alt+F` |
| Action | Keys |
| ------------------------------------------- | ------------------------------------------ |
| Move the cursor to the start of the line. | `Ctrl+A`<br />`Home` |
| Move the cursor to the end of the line. | `Ctrl+E`<br />`End` |
| Move the cursor up one line. | `Up` |
| Move the cursor down one line. | `Down` |
| Move the cursor one character to the left. | `Left` |
| Move the cursor one character to the right. | `Right`<br />`Ctrl+F` |
| Move the cursor one word to the left. | `Ctrl+Left`<br />`Alt+Left`<br />`Alt+B` |
| Move the cursor one word to the right. | `Ctrl+Right`<br />`Alt+Right`<br />`Alt+F` |
#### Editing
| Command | Action | Keys |
| ---------------------- | ------------------------------------------------ | -------------------------------------------------------- |
| `edit.deleteRightAll` | Delete from the cursor to the end of the line. | `Ctrl+K` |
| `edit.deleteLeftAll` | Delete from the cursor to the start of the line. | `Ctrl+U` |
| `edit.clear` | Clear all text in the input field. | `Ctrl+C` |
| `edit.deleteWordLeft` | Delete the previous word. | `Ctrl+Backspace`<br />`Alt+Backspace`<br />`Ctrl+W` |
| `edit.deleteWordRight` | Delete the next word. | `Ctrl+Delete`<br />`Alt+Delete`<br />`Alt+D` |
| `edit.deleteLeft` | Delete the character to the left. | `Backspace`<br />`Ctrl+H` |
| `edit.deleteRight` | Delete the character to the right. | `Delete`<br />`Ctrl+D` |
| `edit.undo` | Undo the most recent text edit. | `Cmd/Win+Z`<br />`Alt+Z` |
| `edit.redo` | Redo the most recent undone text edit. | `Ctrl+Shift+Z`<br />`Shift+Cmd/Win+Z`<br />`Alt+Shift+Z` |
| Action | Keys |
| ------------------------------------------------ | -------------------------------------------------------- |
| Delete from the cursor to the end of the line. | `Ctrl+K` |
| Delete from the cursor to the start of the line. | `Ctrl+U` |
| Clear all text in the input field. | `Ctrl+C` |
| Delete the previous word. | `Ctrl+Backspace`<br />`Alt+Backspace`<br />`Ctrl+W` |
| Delete the next word. | `Ctrl+Delete`<br />`Alt+Delete`<br />`Alt+D` |
| Delete the character to the left. | `Backspace`<br />`Ctrl+H` |
| Delete the character to the right. | `Delete`<br />`Ctrl+D` |
| Undo the most recent text edit. | `Cmd/Win+Z`<br />`Alt+Z` |
| Redo the most recent undone text edit. | `Ctrl+Shift+Z`<br />`Shift+Cmd/Win+Z`<br />`Alt+Shift+Z` |
#### Scrolling
| Command | Action | Keys |
| ----------------- | ------------------------ | ----------------------------- |
| `scroll.up` | Scroll content up. | `Shift+Up` |
| `scroll.down` | Scroll content down. | `Shift+Down` |
| `scroll.home` | Scroll to the top. | `Ctrl+Home`<br />`Shift+Home` |
| `scroll.end` | Scroll to the bottom. | `Ctrl+End`<br />`Shift+End` |
| `scroll.pageUp` | Scroll up by one page. | `Page Up` |
| `scroll.pageDown` | Scroll down by one page. | `Page Down` |
| Action | Keys |
| ------------------------ | ----------------------------- |
| Scroll content up. | `Shift+Up` |
| Scroll content down. | `Shift+Down` |
| Scroll to the top. | `Ctrl+Home`<br />`Shift+Home` |
| Scroll to the bottom. | `Ctrl+End`<br />`Shift+End` |
| Scroll up by one page. | `Page Up` |
| Scroll down by one page. | `Page Down` |
#### History & Search
| Command | Action | Keys |
| ----------------------- | -------------------------------------------- | -------- |
| `history.previous` | Show the previous entry in history. | `Ctrl+P` |
| `history.next` | Show the next entry in history. | `Ctrl+N` |
| `history.search.start` | Start reverse search through history. | `Ctrl+R` |
| `history.search.submit` | Submit the selected reverse-search match. | `Enter` |
| `history.search.accept` | Accept a suggestion while reverse searching. | `Tab` |
| Action | Keys |
| -------------------------------------------- | -------- |
| Show the previous entry in history. | `Ctrl+P` |
| Show the next entry in history. | `Ctrl+N` |
| Start reverse search through history. | `Ctrl+R` |
| Submit the selected reverse-search match. | `Enter` |
| Accept a suggestion while reverse searching. | `Tab` |
#### Navigation
| Command | Action | Keys |
| --------------------- | -------------------------------------------------- | --------------- |
| `nav.up` | Move selection up in lists. | `Up` |
| `nav.down` | Move selection down in lists. | `Down` |
| `nav.dialog.up` | Move up within dialog options. | `Up`<br />`K` |
| `nav.dialog.down` | Move down within dialog options. | `Down`<br />`J` |
| `nav.dialog.next` | Move to the next item or question in a dialog. | `Tab` |
| `nav.dialog.previous` | Move to the previous item or question in a dialog. | `Shift+Tab` |
| Action | Keys |
| -------------------------------------------------- | --------------- |
| Move selection up in lists. | `Up` |
| Move selection down in lists. | `Down` |
| Move up within dialog options. | `Up`<br />`K` |
| Move down within dialog options. | `Down`<br />`J` |
| Move to the next item or question in a dialog. | `Tab` |
| Move to the previous item or question in a dialog. | `Shift+Tab` |
#### Suggestions & Completions
| Command | Action | Keys |
| ----------------------- | --------------------------------------- | -------------------- |
| `suggest.accept` | Accept the inline suggestion. | `Tab`<br />`Enter` |
| `suggest.focusPrevious` | Move to the previous completion option. | `Up`<br />`Ctrl+P` |
| `suggest.focusNext` | Move to the next completion option. | `Down`<br />`Ctrl+N` |
| `suggest.expand` | Expand an inline suggestion. | `Right` |
| `suggest.collapse` | Collapse an inline suggestion. | `Left` |
| Action | Keys |
| --------------------------------------- | -------------------- |
| Accept the inline suggestion. | `Tab`<br />`Enter` |
| Move to the previous completion option. | `Up`<br />`Ctrl+P` |
| Move to the next completion option. | `Down`<br />`Ctrl+N` |
| Expand an inline suggestion. | `Right` |
| Collapse an inline suggestion. | `Left` |
#### Text Input
| Command | Action | Keys |
| -------------------------- | ---------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| `input.submit` | Submit the current prompt. | `Enter` |
| `input.newline` | Insert a newline without submitting. | `Ctrl+Enter`<br />`Cmd/Win+Enter`<br />`Alt+Enter`<br />`Shift+Enter`<br />`Ctrl+J` |
| `input.openExternalEditor` | Open the current prompt or the plan in an external editor. | `Ctrl+X` |
| `input.paste` | Paste from the clipboard. | `Ctrl+V`<br />`Cmd/Win+V`<br />`Alt+V` |
| Action | Keys |
| ---------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| Submit the current prompt. | `Enter` |
| Insert a newline without submitting. | `Ctrl+Enter`<br />`Cmd/Win+Enter`<br />`Alt+Enter`<br />`Shift+Enter`<br />`Ctrl+J` |
| Open the current prompt or the plan in an external editor. | `Ctrl+X` |
| Paste from the clipboard. | `Ctrl+V`<br />`Cmd/Win+V`<br />`Alt+V` |
#### App Controls
| Command | Action | Keys |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
| `app.showErrorDetails` | Toggle detailed error information. | `F12` |
| `app.showFullTodos` | Toggle the full TODO list. | `Ctrl+T` |
| `app.showIdeContextDetail` | Show IDE context details. | `Ctrl+G` |
| `app.toggleMarkdown` | Toggle Markdown rendering. | `Alt+M` |
| `app.toggleCopyMode` | Toggle copy mode when in alternate buffer mode. | `Ctrl+S` |
| `app.toggleYolo` | Toggle YOLO (auto-approval) mode for tool calls. | `Ctrl+Y` |
| `app.cycleApprovalMode` | Cycle through approval modes: default (prompt), auto_edit (auto-approve edits), and plan (read-only). Plan mode is skipped when the agent is busy. | `Shift+Tab` |
| `app.showMoreLines` | Expand and collapse blocks of content when not in alternate buffer mode. | `Ctrl+O` |
| `app.expandPaste` | Expand or collapse a paste placeholder when cursor is over placeholder. | `Ctrl+O` |
| `app.focusShellInput` | Move focus from Gemini to the active shell. | `Tab` |
| `app.unfocusShellInput` | Move focus from the shell back to Gemini. | `Shift+Tab` |
| `app.clearScreen` | Clear the terminal screen and redraw the UI. | `Ctrl+L` |
| `app.restart` | Restart the application. | `R`<br />`Shift+R` |
| `app.suspend` | Suspend the CLI and move it to the background. | `Ctrl+Z` |
| `app.showShellUnfocusWarning` | Show warning when trying to move focus away from shell input. | `Tab` |
| Action | Keys |
| -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
| Toggle detailed error information. | `F12` |
| Toggle the full TODO list. | `Ctrl+T` |
| Show IDE context details. | `Ctrl+G` |
| Toggle Markdown rendering. | `Alt+M` |
| Toggle copy mode when in alternate buffer mode. | `Ctrl+S` |
| Toggle YOLO (auto-approval) mode for tool calls. | `Ctrl+Y` |
| Cycle through approval modes: default (prompt), auto_edit (auto-approve edits), and plan (read-only). Plan mode is skipped when the agent is busy. | `Shift+Tab` |
| Expand and collapse blocks of content when not in alternate buffer mode. | `Ctrl+O` |
| Expand or collapse a paste placeholder when cursor is over placeholder. | `Ctrl+O` |
| Move focus from Gemini to the active shell. | `Tab` |
| Move focus from the shell back to Gemini. | `Shift+Tab` |
| Clear the terminal screen and redraw the UI. | `Ctrl+L` |
| Restart the application. | `R`<br />`Shift+R` |
| Suspend the CLI and move it to the background. | `Ctrl+Z` |
| Show warning when trying to move focus away from shell input. | `Tab` |
#### Background Shell Controls
| Command | Action | Keys |
| --------------------------- | ------------------------------------------------------------------ | ----------- |
| `background.escape` | Dismiss background shell list. | `Esc` |
| `background.select` | Confirm selection in background shell list. | `Enter` |
| `background.toggle` | Toggle current background shell visibility. | `Ctrl+B` |
| `background.toggleList` | Toggle background shell list. | `Ctrl+L` |
| `background.kill` | Kill the active background shell. | `Ctrl+K` |
| `background.unfocus` | Move focus from background shell to Gemini. | `Shift+Tab` |
| `background.unfocusList` | Move focus from background shell list to Gemini. | `Tab` |
| `background.unfocusWarning` | Show warning when trying to move focus away from background shell. | `Tab` |
| Action | Keys |
| ------------------------------------------------------------------ | ----------- |
| Dismiss background shell list. | `Esc` |
| Confirm selection in background shell list. | `Enter` |
| Toggle current background shell visibility. | `Ctrl+B` |
| Toggle background shell list. | `Ctrl+L` |
| Kill the active background shell. | `Ctrl+K` |
| Move focus from background shell to Gemini. | `Shift+Tab` |
| Move focus from background shell list to Gemini. | `Tab` |
| Show warning when trying to move focus away from background shell. | `Tab` |
<!-- KEYBINDINGS-AUTOGEN:END -->
## Customizing Keybindings
You can add alternative keybindings or remove default keybindings by creating a
`keybindings.json` file in your home gemini directory (typically
`~/.gemini/keybindings.json`).
### Configuration Format
The configuration uses a JSON array of objects, similar to VS Code's keybinding
schema. Each object must specify a `command` from the reference tables above and
a `key` combination.
```json
[
{
"command": "edit.clear",
"key": "cmd+l"
},
{
// prefix "-" to unbind a key
"command": "-app.toggleYolo",
"key": "ctrl+y"
},
{
"command": "input.submit",
"key": "ctrl+y"
},
{
// multiple modifiers
"command": "cursor.right",
"key": "shift+alt+a"
},
{
// Some mac keyboards send "Å" instead of "shift+option+a"
"command": "cursor.right",
"key": "Å"
},
{
// some base keys have special multi-char names
"command": "cursor.right",
"key": "shift+pageup"
}
]
```
- **Unbinding** To remove an existing or default keybinding, prefix a minus sign
(`-`) to the `command` name.
- **No Auto-unbinding** The same key can be bound to multiple commands in
different contexts at the same time. Therefore, creating a binding does not
automatically unbind the key from other commands.
- **Explicit Modifiers**: Key matching is explicit. For example, a binding for
`ctrl+f` will only trigger on exactly `ctrl+f`, not `ctrl+shift+f` or
`alt+ctrl+f`.
- **Literal Characters**: Terminals often translate complex key combinations
(especially on macOS with the `Option` key) into special characters, losing
modifier and keystroke information along the way. For example,`shift+5` might
be sent as `%`. In these cases, you must bind to the literal character `%` as
bindings to `shift+5` will never fire. To see precisely what is being sent,
enable `Debug Keystroke Logging` and hit f12 to open the debug log console.
- **Key Modifiers**: The supported key modifiers are:
- `ctrl`
- `shift`,
- `alt` (synonyms: `opt`, `option`)
- `cmd` (synonym: `meta`)
- **Base Key**: The base key can be any single unicode code point or any of the
following special keys:
- **Navigation**: `up`, `down`, `left`, `right`, `home`, `end`, `pageup`,
`pagedown`
- **Actions**: `enter`, `escape`, `tab`, `space`, `backspace`, `delete`,
`clear`, `insert`, `printscreen`
- **Toggles**: `capslock`, `numlock`, `scrolllock`, `pausebreak`
- **Function Keys**: `f1` through `f35`
- **Numpad**: `numpad0` through `numpad9`, `numpad_add`, `numpad_subtract`,
`numpad_multiply`, `numpad_divide`, `numpad_decimal`, `numpad_separator`
## Additional context-specific shortcuts
- `Option+B/F/M` (macOS only): Are interpreted as `Cmd+B/F/M` even if your
@@ -229,9 +154,6 @@ a `key` combination.
the numbered radio option and confirm when the full number is entered.
- `Ctrl + O`: Expand or collapse paste placeholders (`[Pasted Text: X lines]`)
inline when the cursor is over the placeholder.
- `Ctrl + X` (while a plan is presented): Open the plan in an external editor to
[collaboratively edit or comment](../cli/plan-mode.md#collaborative-plan-editing)
on the implementation strategy.
- `Double-click` on a paste placeholder (alternate buffer mode only): Expand to
view full content inline. Double-click again to collapse.
+7 -33
View File
@@ -191,13 +191,9 @@ User, and (if configured) Admin directories.
#### System-wide policies (Admin)
Administrators can enforce system-wide policies (Tier 4) that override all user
and default settings. These policies can be loaded from standard system
locations or supplemental paths.
##### Standard Locations
These are the default paths the CLI searches for admin policies:
Administrators can enforce system-wide policies (Tier 3) that override all user
and default settings. These policies must be placed in specific, secure
directories:
| OS | Policy Directory Path |
| :---------- | :------------------------------------------------ |
@@ -205,25 +201,10 @@ These are the default paths the CLI searches for admin policies:
| **macOS** | `/Library/Application Support/GeminiCli/policies` |
| **Windows** | `C:\ProgramData\gemini-cli\policies` |
##### Supplemental Admin Policies
**Security Requirements:**
Administrators can also specify supplemental policy paths using:
- The `--admin-policy` command-line flag.
- The `adminPolicyPaths` setting in a system settings file.
These supplemental policies are assigned the same **Admin** tier (Base 4) as
policies in standard locations.
**Security Guard**: Supplemental admin policies are **ignored** if any `.toml`
policy files are found in the standard system location. This prevents flag-based
overrides when a central system policy has already been established.
#### Security Requirements
To prevent privilege escalation, the CLI enforces strict security checks on the
**standard system policy directory**. If checks fail, the policies in that
directory are **ignored**.
To prevent privilege escalation, the CLI enforces strict security checks on
admin directories. If checks fail, system policies are **ignored**.
- **Linux / macOS:** Must be owned by `root` (UID 0) and NOT writable by group
or others (e.g., `chmod 755`).
@@ -233,11 +214,6 @@ directory are **ignored**.
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.
### TOML rule schema
Here is a breakdown of the fields available in a TOML policy rule:
@@ -342,9 +318,7 @@ policies, as it is much more robust than manually writing Fully Qualified Names
**1. Targeting a specific tool on a server**
Combine `mcpName` and `toolName` to target a single operation. When using
`mcpName`, the `toolName` field should strictly be the simple name of the tool
(e.g., `search`), **not** the Fully Qualified Name (e.g., `mcp_server_search`).
Combine `mcpName` and `toolName` to target a single operation.
```toml
# Allows the `search` tool on the `my-jira-server` MCP
-15
View File
@@ -124,21 +124,6 @@ topics on:
`advanced.excludedEnvVars` setting in your `settings.json` to exclude fewer
variables.
- **Warning: `npm WARN deprecated node-domexception@1.0.0` or
`npm WARN deprecated glob` during install/update**
- **Issue:** When installing or updating the Gemini CLI globally via
`npm install -g @google/gemini-cli` or `npm update -g @google/gemini-cli`,
you might see deprecation warnings regarding `node-domexception` or old
versions of `glob`.
- **Cause:** These warnings occur because some dependencies (or their
sub-dependencies, like `google-auth-library`) rely on older package
versions. Since Gemini CLI requires Node.js 20 or higher, the platform's
native features (like the native `DOMException`) are used, making these
warnings purely informational.
- **Solution:** These warnings are harmless and can be safely ignored. Your
installation or update will complete successfully and function properly
without any action required.
## Exit codes
The Gemini CLI uses specific exit codes to indicate the reason for termination.
-10
View File
@@ -47,11 +47,6 @@
"label": "Plan tasks with todos",
"slug": "docs/cli/tutorials/task-planning"
},
{
"label": "Use Plan Mode with model steering",
"badge": "🔬",
"slug": "docs/cli/tutorials/plan-mode-steering"
},
{
"label": "Web search and fetch",
"slug": "docs/cli/tutorials/web-tools"
@@ -111,11 +106,6 @@
{ "label": "MCP servers", "slug": "docs/tools/mcp-server" },
{ "label": "Model routing", "slug": "docs/cli/model-routing" },
{ "label": "Model selection", "slug": "docs/cli/model" },
{
"label": "Model steering",
"badge": "🔬",
"slug": "docs/cli/model-steering"
},
{
"label": "Notifications",
"badge": "🔬",
+2 -2
View File
@@ -1011,7 +1011,7 @@ gemini mcp add [options] <name> <commandOrUrl> [args...]
**Options (flags):**
- `-s, --scope`: Configuration scope (user or project). [default: "project"]
- `-s, --scope`: Configuration scope (user or workspace). [default: "workspace"]
- `-t, --transport`: Transport type (stdio, sse, http). [default: "stdio"]
- `-e, --env`: Set environment variables (e.g. -e KEY=value).
- `-H, --header`: Set HTTP headers for SSE and HTTP transports (e.g. -H
@@ -1122,7 +1122,7 @@ gemini mcp remove <name>
**Options (flags):**
- `-s, --scope`: Configuration scope (user or project). [default: "project"]
- `-s, --scope`: Configuration scope (user or workspace). [default: "workspace"]
**Example:**
-8
View File
@@ -120,14 +120,6 @@ tools to detect if they are being run from within the Gemini CLI.
## Command restrictions
<!-- prettier-ignore -->
> [!WARNING]
> The `tools.core` setting is an **allowlist for _all_ built-in
> tools**, not just shell commands. When you set `tools.core` to any value,
> _only_ the tools explicitly listed will be enabled. This includes all built-in
> tools like `read_file`, `write_file`, `glob`, `grep_search`, `list_directory`,
> `replace`, etc.
You can restrict the commands that can be executed by the `run_shell_command`
tool by using the `tools.core` and `tools.exclude` settings in your
configuration file.
+4 -9
View File
@@ -82,14 +82,11 @@ const commonAliases = {
const cliConfig = {
...baseConfig,
banner: {
js: `const require = (await import('node:module')).createRequire(import.meta.url); const __chunk_filename = (await import('node:url')).fileURLToPath(import.meta.url); const __chunk_dirname = (await import('node:path')).dirname(__chunk_filename);`,
js: `const require = (await import('node:module')).createRequire(import.meta.url); globalThis.__filename = (await import('node:url')).fileURLToPath(import.meta.url); globalThis.__dirname = (await import('node:path')).dirname(globalThis.__filename);`,
},
entryPoints: { gemini: 'packages/cli/index.ts' },
outdir: 'bundle',
splitting: true,
entryPoints: ['packages/cli/index.ts'],
outfile: 'bundle/gemini.js',
define: {
__filename: '__chunk_filename',
__dirname: '__chunk_dirname',
'process.env.CLI_VERSION': JSON.stringify(pkg.version),
'process.env.GEMINI_SANDBOX_IMAGE_DEFAULT': JSON.stringify(
pkg.config?.sandboxImageUri,
@@ -106,13 +103,11 @@ const cliConfig = {
const a2aServerConfig = {
...baseConfig,
banner: {
js: `const require = (await import('node:module')).createRequire(import.meta.url); const __chunk_filename = (await import('node:url')).fileURLToPath(import.meta.url); const __chunk_dirname = (await import('node:path')).dirname(__chunk_filename);`,
js: `const require = (await import('node:module')).createRequire(import.meta.url); globalThis.__filename = (await import('node:url')).fileURLToPath(import.meta.url); globalThis.__dirname = (await import('node:path')).dirname(globalThis.__filename);`,
},
entryPoints: ['packages/a2a-server/src/http/server.ts'],
outfile: 'packages/a2a-server/dist/a2a-server.mjs',
define: {
__filename: '__chunk_filename',
__dirname: '__chunk_dirname',
'process.env.CLI_VERSION': JSON.stringify(pkg.version),
},
plugins: createWasmPlugins(),
+5
View File
@@ -35,6 +35,11 @@ const commonRestrictedSyntaxRules = [
message:
'Do not throw string literals or non-Error objects. Throw new Error("...") instead.',
},
{
selector: 'CallExpression[callee.name="fetch"]',
message:
'Use safeFetch() from "@/utils/fetch" instead of the global fetch() to ensure SSRF protection. If you are implementing a custom security layer, use an eslint-disable comment and explain why.',
},
];
export default tseslint.config(
@@ -1,64 +0,0 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, beforeEach, afterEach } from 'vitest';
import { TestRig } from './test-helper.js';
/**
* integration test to ensure no node.js deprecation warnings are emitted.
* must run for all supported node versions as warnings may vary by version.
*/
describe('deprecation-warnings', () => {
let rig: TestRig;
beforeEach(() => {
rig = new TestRig();
});
afterEach(async () => await rig.cleanup());
it.each([
{ command: '--version', description: 'running --version' },
{ command: '--help', description: 'running with --help' },
])(
'should not emit any deprecation warnings when $description',
async ({ command, description }) => {
await rig.setup(
`should not emit any deprecation warnings when ${description}`,
);
const { stderr, exitCode } = await rig.runWithStreams([command]);
// node.js deprecation warnings: (node:12345) [DEP0040] DeprecationWarning: ...
const deprecationWarningPattern = /\[DEP\d+\].*DeprecationWarning/i;
const hasDeprecationWarning = deprecationWarningPattern.test(stderr);
if (hasDeprecationWarning) {
const deprecationMatches = stderr.match(
/\[DEP\d+\].*DeprecationWarning:.*/gi,
);
const warnings = deprecationMatches
? deprecationMatches.map((m) => m.trim()).join('\n')
: 'Unknown deprecation warning format';
throw new Error(
`Deprecation warnings detected in CLI output:\n${warnings}\n\n` +
`Full stderr:\n${stderr}\n\n` +
`This test ensures no deprecated Node.js modules are used. ` +
`Please update dependencies to use non-deprecated alternatives.`,
);
}
// only check exit code if no deprecation warnings found
if (exitCode !== 0) {
throw new Error(
`CLI exited with code ${exitCode} (expected 0). This may indicate a setup issue.\n` +
`Stderr: ${stderr}`,
);
}
},
);
});
-2
View File
@@ -1,2 +0,0 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"run_shell_command","args":{"command":"ls -F"}}}]},"finishReason":"STOP","index":0}]},{"candidates":[{"content":{"parts":[{"text":"I ran ls -F"}]},"finishReason":"STOP","index":0}]}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I ran ls -F"}]},"finishReason":"STOP","index":0}]}]}
-81
View File
@@ -1,81 +0,0 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { join } from 'node:path';
import { TestRig, GEMINI_DIR } from './test-helper.js';
import fs from 'node:fs';
describe('User Policy Regression Repro', () => {
let rig: TestRig;
beforeEach(() => {
rig = new TestRig();
});
afterEach(async () => {
if (rig) {
await rig.cleanup();
}
});
it('should respect policies in ~/.gemini/policies/allowed-tools.toml', async () => {
rig.setup('user-policy-test', {
fakeResponsesPath: join(import.meta.dirname, 'user-policy.responses'),
});
// Create ~/.gemini/policies/allowed-tools.toml
const userPoliciesDir = join(rig.homeDir!, GEMINI_DIR, 'policies');
fs.mkdirSync(userPoliciesDir, { recursive: true });
fs.writeFileSync(
join(userPoliciesDir, 'allowed-tools.toml'),
`
[[rule]]
toolName = "run_shell_command"
commandPrefix = "ls -F"
decision = "allow"
priority = 100
`,
);
// Run gemini with a prompt that triggers ls -F
// approvalMode: 'default' in headless mode will DENY if it hits ASK_USER
const result = await rig.run({
args: ['-p', 'Run ls -F', '--model', 'gemini-3.1-pro-preview'],
approvalMode: 'default',
});
expect(result).toContain('I ran ls -F');
expect(result).not.toContain('Tool execution denied by policy');
expect(result).not.toContain('Tool "run_shell_command" not found');
const toolLogs = rig.readToolLogs();
const lsLog = toolLogs.find(
(l) =>
l.toolRequest.name === 'run_shell_command' &&
l.toolRequest.args.includes('ls -F'),
);
expect(lsLog).toBeDefined();
expect(lsLog?.toolRequest.success).toBe(true);
});
it('should FAIL if policy is not present (sanity check)', async () => {
rig.setup('user-policy-sanity-check', {
fakeResponsesPath: join(import.meta.dirname, 'user-policy.responses'),
});
// DO NOT create the policy file here
// Run gemini with a prompt that triggers ls -F
const result = await rig.run({
args: ['-p', 'Run ls -F', '--model', 'gemini-3.1-pro-preview'],
approvalMode: 'default',
});
// In non-interactive mode, it should be denied
expect(result).toContain('Tool "run_shell_command" not found');
});
});
+151 -232
View File
@@ -1,12 +1,12 @@
{
"name": "@google/gemini-cli",
"version": "0.35.0-nightly.20260311.657f19c1f",
"version": "0.34.0-nightly.20260310.4653b126f",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@google/gemini-cli",
"version": "0.35.0-nightly.20260311.657f19c1f",
"version": "0.34.0-nightly.20260310.4653b126f",
"workspaces": [
"packages/*"
],
@@ -1318,9 +1318,9 @@
}
},
"node_modules/@google-cloud/storage": {
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.19.0.tgz",
"integrity": "sha512-n2FjE7NAOYyshogdc7KQOl/VZb4sneqPjWouSyia9CMDdMhRX5+RIbqalNmC7LOLzuLAN89VlF2HvG8na9G+zQ==",
"version": "7.17.0",
"resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.17.0.tgz",
"integrity": "sha512-5m9GoZqKh52a1UqkxDBu/+WVFDALNtHg5up5gNmNbXQWBcV813tzJKsyDtKjOPrlR1em1TxtD7NSPCrObH7koQ==",
"license": "Apache-2.0",
"dependencies": {
"@google-cloud/paginator": "^5.0.0",
@@ -1329,7 +1329,7 @@
"abort-controller": "^3.0.0",
"async-retry": "^1.3.3",
"duplexify": "^4.1.3",
"fast-xml-parser": "^5.3.4",
"fast-xml-parser": "^4.4.1",
"gaxios": "^6.0.2",
"google-auth-library": "^9.6.3",
"html-entities": "^2.5.2",
@@ -1516,9 +1516,9 @@
}
},
"node_modules/@hono/node-server": {
"version": "1.19.11",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz",
"integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==",
"version": "1.19.9",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz",
"integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==",
"license": "MIT",
"engines": {
"node": ">=18.14.1"
@@ -2089,9 +2089,9 @@
}
},
"node_modules/@modelcontextprotocol/sdk/node_modules/ajv": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
@@ -3039,9 +3039,9 @@
"license": "BSD-3-Clause"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
"integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.2.tgz",
"integrity": "sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA==",
"cpu": [
"arm"
],
@@ -3052,9 +3052,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
"integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.2.tgz",
"integrity": "sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g==",
"cpu": [
"arm64"
],
@@ -3065,9 +3065,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
"integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.2.tgz",
"integrity": "sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ==",
"cpu": [
"arm64"
],
@@ -3078,9 +3078,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
"integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.2.tgz",
"integrity": "sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw==",
"cpu": [
"x64"
],
@@ -3091,9 +3091,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
"integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.2.tgz",
"integrity": "sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA==",
"cpu": [
"arm64"
],
@@ -3104,9 +3104,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
"integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.2.tgz",
"integrity": "sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA==",
"cpu": [
"x64"
],
@@ -3117,9 +3117,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
"integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.2.tgz",
"integrity": "sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==",
"cpu": [
"arm"
],
@@ -3130,9 +3130,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
"integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.2.tgz",
"integrity": "sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==",
"cpu": [
"arm"
],
@@ -3143,9 +3143,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
"integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.2.tgz",
"integrity": "sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==",
"cpu": [
"arm64"
],
@@ -3156,9 +3156,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
"integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.2.tgz",
"integrity": "sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==",
"cpu": [
"arm64"
],
@@ -3169,22 +3169,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
"integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
"cpu": [
"loong64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-loong64-musl": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
"integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.2.tgz",
"integrity": "sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==",
"cpu": [
"loong64"
],
@@ -3195,22 +3182,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
"integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
"cpu": [
"ppc64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-ppc64-musl": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
"integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.2.tgz",
"integrity": "sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==",
"cpu": [
"ppc64"
],
@@ -3221,9 +3195,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
"integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.2.tgz",
"integrity": "sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==",
"cpu": [
"riscv64"
],
@@ -3234,9 +3208,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
"integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.2.tgz",
"integrity": "sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==",
"cpu": [
"riscv64"
],
@@ -3247,9 +3221,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
"integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.2.tgz",
"integrity": "sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==",
"cpu": [
"s390x"
],
@@ -3260,9 +3234,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
"integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.2.tgz",
"integrity": "sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==",
"cpu": [
"x64"
],
@@ -3273,9 +3247,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
"integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.2.tgz",
"integrity": "sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==",
"cpu": [
"x64"
],
@@ -3285,23 +3259,10 @@
"linux"
]
},
"node_modules/@rollup/rollup-openbsd-x64": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
"integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"openbsd"
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
"integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.2.tgz",
"integrity": "sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==",
"cpu": [
"arm64"
],
@@ -3312,9 +3273,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
"integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.2.tgz",
"integrity": "sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA==",
"cpu": [
"arm64"
],
@@ -3325,9 +3286,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
"integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.2.tgz",
"integrity": "sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg==",
"cpu": [
"ia32"
],
@@ -3338,9 +3299,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
"integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.2.tgz",
"integrity": "sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw==",
"cpu": [
"x64"
],
@@ -3351,9 +3312,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
"integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.2.tgz",
"integrity": "sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA==",
"cpu": [
"x64"
],
@@ -3408,9 +3369,9 @@
}
},
"node_modules/@secretlint/config-loader/node_modules/ajv": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5270,9 +5231,9 @@
}
},
"node_modules/ajv": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5304,9 +5265,9 @@
}
},
"node_modules/ajv-formats/node_modules/ajv": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
@@ -8314,12 +8275,12 @@
}
},
"node_modules/express-rate-limit": {
"version": "8.3.1",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz",
"integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==",
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz",
"integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==",
"license": "MIT",
"dependencies": {
"ip-address": "10.1.0"
"ip-address": "10.0.1"
},
"engines": {
"node": ">= 16"
@@ -8461,25 +8422,10 @@
],
"license": "BSD-3-Clause"
},
"node_modules/fast-xml-builder": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.2.tgz",
"integrity": "sha512-NJAmiuVaJEjVa7TjLZKlYd7RqmzOC91EtPFXHvlTcqBVo50Qh7XV5IwvXi1c7NRz2Q/majGX9YLcwJtWgHjtkA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT",
"dependencies": {
"path-expression-matcher": "^1.1.3"
}
},
"node_modules/fast-xml-parser": {
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.3.tgz",
"integrity": "sha512-Ymnuefk6VzAhT3SxLzVUw+nMio/wB1NGypHkgetwtXcK1JfryaHk4DWQFGVwQ9XgzyS5iRZ7C2ZGI4AMsdMZ6A==",
"version": "4.5.3",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz",
"integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==",
"funding": [
{
"type": "github",
@@ -8488,9 +8434,7 @@
],
"license": "MIT",
"dependencies": {
"fast-xml-builder": "^1.1.2",
"path-expression-matcher": "^1.1.3",
"strnum": "^2.1.2"
"strnum": "^1.1.1"
},
"bin": {
"fxparser": "src/cli/cli.js"
@@ -9555,9 +9499,9 @@
}
},
"node_modules/hono": {
"version": "4.12.7",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.7.tgz",
"integrity": "sha512-jq9l1DM0zVIvsm3lv9Nw9nlJnMNPOcAtsbsgiUhWcFzPE99Gvo6yRTlszSLLYacMeQ6quHD6hMfId8crVHvexw==",
"version": "4.11.9",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.11.9.tgz",
"integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==",
"license": "MIT",
"engines": {
"node": ">=16.9.0"
@@ -10006,9 +9950,9 @@
}
},
"node_modules/ip-address": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
"integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz",
"integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==",
"license": "MIT",
"engines": {
"node": ">= 12"
@@ -10792,7 +10736,6 @@
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true,
"license": "MIT"
},
"node_modules/json-schema-typed": {
@@ -12871,21 +12814,6 @@
"node": ">=8"
}
},
"node_modules/path-expression-matcher": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.1.3.tgz",
"integrity": "sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -13919,9 +13847,9 @@
}
},
"node_modules/rollup": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
"integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.2.tgz",
"integrity": "sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==",
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.8"
@@ -13934,31 +13862,28 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.59.0",
"@rollup/rollup-android-arm64": "4.59.0",
"@rollup/rollup-darwin-arm64": "4.59.0",
"@rollup/rollup-darwin-x64": "4.59.0",
"@rollup/rollup-freebsd-arm64": "4.59.0",
"@rollup/rollup-freebsd-x64": "4.59.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
"@rollup/rollup-linux-arm-musleabihf": "4.59.0",
"@rollup/rollup-linux-arm64-gnu": "4.59.0",
"@rollup/rollup-linux-arm64-musl": "4.59.0",
"@rollup/rollup-linux-loong64-gnu": "4.59.0",
"@rollup/rollup-linux-loong64-musl": "4.59.0",
"@rollup/rollup-linux-ppc64-gnu": "4.59.0",
"@rollup/rollup-linux-ppc64-musl": "4.59.0",
"@rollup/rollup-linux-riscv64-gnu": "4.59.0",
"@rollup/rollup-linux-riscv64-musl": "4.59.0",
"@rollup/rollup-linux-s390x-gnu": "4.59.0",
"@rollup/rollup-linux-x64-gnu": "4.59.0",
"@rollup/rollup-linux-x64-musl": "4.59.0",
"@rollup/rollup-openbsd-x64": "4.59.0",
"@rollup/rollup-openharmony-arm64": "4.59.0",
"@rollup/rollup-win32-arm64-msvc": "4.59.0",
"@rollup/rollup-win32-ia32-msvc": "4.59.0",
"@rollup/rollup-win32-x64-gnu": "4.59.0",
"@rollup/rollup-win32-x64-msvc": "4.59.0",
"@rollup/rollup-android-arm-eabi": "4.53.2",
"@rollup/rollup-android-arm64": "4.53.2",
"@rollup/rollup-darwin-arm64": "4.53.2",
"@rollup/rollup-darwin-x64": "4.53.2",
"@rollup/rollup-freebsd-arm64": "4.53.2",
"@rollup/rollup-freebsd-x64": "4.53.2",
"@rollup/rollup-linux-arm-gnueabihf": "4.53.2",
"@rollup/rollup-linux-arm-musleabihf": "4.53.2",
"@rollup/rollup-linux-arm64-gnu": "4.53.2",
"@rollup/rollup-linux-arm64-musl": "4.53.2",
"@rollup/rollup-linux-loong64-gnu": "4.53.2",
"@rollup/rollup-linux-ppc64-gnu": "4.53.2",
"@rollup/rollup-linux-riscv64-gnu": "4.53.2",
"@rollup/rollup-linux-riscv64-musl": "4.53.2",
"@rollup/rollup-linux-s390x-gnu": "4.53.2",
"@rollup/rollup-linux-x64-gnu": "4.53.2",
"@rollup/rollup-linux-x64-musl": "4.53.2",
"@rollup/rollup-openharmony-arm64": "4.53.2",
"@rollup/rollup-win32-arm64-msvc": "4.53.2",
"@rollup/rollup-win32-ia32-msvc": "4.53.2",
"@rollup/rollup-win32-x64-gnu": "4.53.2",
"@rollup/rollup-win32-x64-msvc": "4.53.2",
"fsevents": "~2.3.2"
}
},
@@ -14492,9 +14417,9 @@
}
},
"node_modules/simple-git": {
"version": "3.33.0",
"resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.33.0.tgz",
"integrity": "sha512-D4V/tGC2sjsoNhoMybKyGoE+v8A60hRawKQ1iFRA1zwuDgGZCBJ4ByOzZ5J8joBbi4Oam0qiPH+GhzmSBwbJng==",
"version": "3.28.0",
"resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.28.0.tgz",
"integrity": "sha512-Rs/vQRwsn1ILH1oBUy8NucJlXmnnLeLCfcvbSehkPzbv3wwoFWIdtfd6Ndo6ZPhlPsCZ60CPI4rxurnwAa+a2w==",
"license": "MIT",
"dependencies": {
"@kwsites/file-exists": "^1.1.1",
@@ -14997,9 +14922,9 @@
}
},
"node_modules/strnum": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.0.tgz",
"integrity": "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==",
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz",
"integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==",
"funding": [
{
"type": "github",
@@ -15179,9 +15104,9 @@
}
},
"node_modules/systeminformation": {
"version": "5.31.4",
"resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.31.4.tgz",
"integrity": "sha512-lZppDyQx91VdS5zJvAyGkmwe+Mq6xY978BDUG2wRkWE+jkmUF5ti8cvOovFQoN5bvSFKCXVkyKEaU5ec3SJiRg==",
"version": "5.30.2",
"resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.30.2.tgz",
"integrity": "sha512-Rrt5oFTWluUVuPlbtn3o9ja+nvjdF3Um4DG0KxqfYvpzcx7Q9plZBTjJiJy9mAouua4+OI7IUGBaG9Zyt9NgxA==",
"license": "MIT",
"os": [
"darwin",
@@ -15222,9 +15147,9 @@
}
},
"node_modules/table/node_modules/ajv": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -15945,9 +15870,9 @@
}
},
"node_modules/underscore": {
"version": "1.13.8",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz",
"integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==",
"version": "1.13.7",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz",
"integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==",
"dev": true,
"license": "MIT"
},
@@ -16890,7 +16815,7 @@
},
"packages/a2a-server": {
"name": "@google/gemini-cli-a2a-server",
"version": "0.35.0-nightly.20260311.657f19c1f",
"version": "0.34.0-nightly.20260310.4653b126f",
"dependencies": {
"@a2a-js/sdk": "0.3.11",
"@google-cloud/storage": "^7.16.0",
@@ -17005,7 +16930,7 @@
},
"packages/cli": {
"name": "@google/gemini-cli",
"version": "0.35.0-nightly.20260311.657f19c1f",
"version": "0.34.0-nightly.20260310.4653b126f",
"license": "Apache-2.0",
"dependencies": {
"@agentclientprotocol/sdk": "^0.12.0",
@@ -17177,7 +17102,7 @@
},
"packages/core": {
"name": "@google/gemini-cli-core",
"version": "0.35.0-nightly.20260311.657f19c1f",
"version": "0.34.0-nightly.20260310.4653b126f",
"license": "Apache-2.0",
"dependencies": {
"@a2a-js/sdk": "0.3.11",
@@ -17341,9 +17266,9 @@
}
},
"packages/core/node_modules/ajv": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
@@ -17391,12 +17316,6 @@
"node": ">= 4"
}
},
"packages/core/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"license": "MIT"
},
"packages/core/node_modules/mime": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/mime/-/mime-4.0.7.tgz",
@@ -17439,7 +17358,7 @@
},
"packages/devtools": {
"name": "@google/gemini-cli-devtools",
"version": "0.35.0-nightly.20260311.657f19c1f",
"version": "0.34.0-nightly.20260310.4653b126f",
"license": "Apache-2.0",
"dependencies": {
"ws": "^8.16.0"
@@ -17454,7 +17373,7 @@
},
"packages/sdk": {
"name": "@google/gemini-cli-sdk",
"version": "0.35.0-nightly.20260311.657f19c1f",
"version": "0.34.0-nightly.20260310.4653b126f",
"license": "Apache-2.0",
"dependencies": {
"@google/gemini-cli-core": "file:../core",
@@ -17471,7 +17390,7 @@
},
"packages/test-utils": {
"name": "@google/gemini-cli-test-utils",
"version": "0.35.0-nightly.20260311.657f19c1f",
"version": "0.34.0-nightly.20260310.4653b126f",
"license": "Apache-2.0",
"dependencies": {
"@google/gemini-cli-core": "file:../core",
@@ -17488,7 +17407,7 @@
},
"packages/vscode-ide-companion": {
"name": "gemini-cli-vscode-ide-companion",
"version": "0.35.0-nightly.20260311.657f19c1f",
"version": "0.34.0-nightly.20260310.4653b126f",
"license": "LICENSE",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.23.0",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli",
"version": "0.35.0-nightly.20260311.657f19c1f",
"version": "0.34.0-nightly.20260310.4653b126f",
"engines": {
"node": ">=20.0.0"
},
@@ -14,7 +14,7 @@
"url": "git+https://github.com/google-gemini/gemini-cli.git"
},
"config": {
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.35.0-nightly.20260311.657f19c1f"
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.34.0-nightly.20260310.4653b126f"
},
"scripts": {
"start": "cross-env NODE_ENV=development node scripts/start.js",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-a2a-server",
"version": "0.35.0-nightly.20260311.657f19c1f",
"version": "0.34.0-nightly.20260310.4653b126f",
"description": "Gemini CLI A2A Server",
"repository": {
"type": "git",
@@ -26,7 +26,7 @@ describe('Task Event-Driven Scheduler', () => {
mockConfig = createMockConfig({
isEventDrivenSchedulerEnabled: () => true,
}) as Config;
messageBus = mockConfig.messageBus;
messageBus = mockConfig.getMessageBus();
mockEventBus = {
publish: vi.fn(),
on: vi.fn(),
@@ -360,7 +360,7 @@ describe('Task Event-Driven Scheduler', () => {
isEventDrivenSchedulerEnabled: () => true,
getApprovalMode: () => ApprovalMode.YOLO,
}) as Config;
const yoloMessageBus = yoloConfig.messageBus;
const yoloMessageBus = yoloConfig.getMessageBus();
// @ts-expect-error - Calling private constructor
const task = new Task('task-id', 'context-id', yoloConfig, mockEventBus);
+220 -1
View File
@@ -4,14 +4,25 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
import {
describe,
it,
expect,
vi,
beforeEach,
afterEach,
type Mock,
} from 'vitest';
import { Task } from './task.js';
import {
GeminiEventType,
ApprovalMode,
ToolConfirmationOutcome,
type Config,
type ToolCallRequestInfo,
type GitService,
type CompletedToolCall,
type ToolCall,
} from '@google/gemini-cli-core';
import { createMockConfig } from '../utils/testing_utils.js';
import type { ExecutionEventBus, RequestContext } from '@a2a-js/sdk/server';
@@ -378,6 +389,214 @@ describe('Task', () => {
);
});
describe('_schedulerToolCallsUpdate', () => {
let task: Task;
type SpyInstance = ReturnType<typeof vi.spyOn>;
let setTaskStateAndPublishUpdateSpy: SpyInstance;
let mockConfig: Config;
let mockEventBus: ExecutionEventBus;
beforeEach(() => {
mockConfig = createMockConfig() as Config;
mockEventBus = {
publish: vi.fn(),
on: vi.fn(),
off: vi.fn(),
once: vi.fn(),
removeAllListeners: vi.fn(),
finished: vi.fn(),
};
// @ts-expect-error - Calling private constructor
task = new Task('task-id', 'context-id', mockConfig, mockEventBus);
// Spy on the method we want to check calls for
setTaskStateAndPublishUpdateSpy = vi.spyOn(
task,
'setTaskStateAndPublishUpdate',
);
});
afterEach(() => {
vi.restoreAllMocks();
});
it('should set state to input-required when a tool is awaiting approval and none are executing', () => {
const toolCalls = [
{ request: { callId: '1' }, status: 'awaiting_approval' },
] as ToolCall[];
// @ts-expect-error - Calling private method
task._schedulerToolCallsUpdate(toolCalls);
// The last call should be the final state update
expect(setTaskStateAndPublishUpdateSpy).toHaveBeenLastCalledWith(
'input-required',
{ kind: 'state-change' },
undefined,
undefined,
true, // final: true
);
});
it('should NOT set state to input-required if a tool is awaiting approval but another is executing', () => {
const toolCalls = [
{ request: { callId: '1' }, status: 'awaiting_approval' },
{ request: { callId: '2' }, status: 'executing' },
] as ToolCall[];
// @ts-expect-error - Calling private method
task._schedulerToolCallsUpdate(toolCalls);
// It will be called for status updates, but not with final: true
const finalCall = setTaskStateAndPublishUpdateSpy.mock.calls.find(
(call) => call[4] === true,
);
expect(finalCall).toBeUndefined();
});
it('should set state to input-required once an executing tool finishes, leaving one awaiting approval', () => {
const initialToolCalls = [
{ request: { callId: '1' }, status: 'awaiting_approval' },
{ request: { callId: '2' }, status: 'executing' },
] as ToolCall[];
// @ts-expect-error - Calling private method
task._schedulerToolCallsUpdate(initialToolCalls);
// No final call yet
let finalCall = setTaskStateAndPublishUpdateSpy.mock.calls.find(
(call) => call[4] === true,
);
expect(finalCall).toBeUndefined();
// Now, the executing tool finishes. The scheduler would call _resolveToolCall for it.
// @ts-expect-error - Calling private method
task._resolveToolCall('2');
// Then another update comes in for the awaiting tool (e.g., a re-check)
const subsequentToolCalls = [
{ request: { callId: '1' }, status: 'awaiting_approval' },
] as ToolCall[];
// @ts-expect-error - Calling private method
task._schedulerToolCallsUpdate(subsequentToolCalls);
// NOW we should get the final call
finalCall = setTaskStateAndPublishUpdateSpy.mock.calls.find(
(call) => call[4] === true,
);
expect(finalCall).toBeDefined();
expect(finalCall?.[0]).toBe('input-required');
});
it('should NOT set state to input-required if skipFinalTrueAfterInlineEdit is true', () => {
task.skipFinalTrueAfterInlineEdit = true;
const toolCalls = [
{ request: { callId: '1' }, status: 'awaiting_approval' },
] as ToolCall[];
// @ts-expect-error - Calling private method
task._schedulerToolCallsUpdate(toolCalls);
const finalCall = setTaskStateAndPublishUpdateSpy.mock.calls.find(
(call) => call[4] === true,
);
expect(finalCall).toBeUndefined();
});
describe('auto-approval', () => {
it('should NOT publish ToolCallConfirmationEvent when autoExecute is true', () => {
task.autoExecute = true;
const onConfirmSpy = vi.fn();
const toolCalls = [
{
request: { callId: '1' },
status: 'awaiting_approval',
correlationId: 'test-corr-id',
confirmationDetails: {
type: 'edit',
onConfirm: onConfirmSpy,
},
},
] as unknown as ToolCall[];
// @ts-expect-error - Calling private method
task._schedulerToolCallsUpdate(toolCalls);
expect(onConfirmSpy).toHaveBeenCalledWith(
ToolConfirmationOutcome.ProceedOnce,
);
const calls = (mockEventBus.publish as Mock).mock.calls;
// Search if ToolCallConfirmationEvent was published
const confEvent = calls.find(
(call) =>
call[0].metadata?.coderAgent?.kind ===
CoderAgentEvent.ToolCallConfirmationEvent,
);
expect(confEvent).toBeUndefined();
});
it('should NOT publish ToolCallConfirmationEvent when approval mode is YOLO', () => {
(mockConfig.getApprovalMode as Mock).mockReturnValue(ApprovalMode.YOLO);
task.autoExecute = false;
const onConfirmSpy = vi.fn();
const toolCalls = [
{
request: { callId: '1' },
status: 'awaiting_approval',
correlationId: 'test-corr-id',
confirmationDetails: {
type: 'edit',
onConfirm: onConfirmSpy,
},
},
] as unknown as ToolCall[];
// @ts-expect-error - Calling private method
task._schedulerToolCallsUpdate(toolCalls);
expect(onConfirmSpy).toHaveBeenCalledWith(
ToolConfirmationOutcome.ProceedOnce,
);
const calls = (mockEventBus.publish as Mock).mock.calls;
// Search if ToolCallConfirmationEvent was published
const confEvent = calls.find(
(call) =>
call[0].metadata?.coderAgent?.kind ===
CoderAgentEvent.ToolCallConfirmationEvent,
);
expect(confEvent).toBeUndefined();
});
it('should NOT auto-approve when autoExecute is false and mode is not YOLO', () => {
task.autoExecute = false;
(mockConfig.getApprovalMode as Mock).mockReturnValue(
ApprovalMode.DEFAULT,
);
const onConfirmSpy = vi.fn();
const toolCalls = [
{
request: { callId: '1' },
status: 'awaiting_approval',
confirmationDetails: { onConfirm: onConfirmSpy },
},
] as unknown as ToolCall[];
// @ts-expect-error - Calling private method
task._schedulerToolCallsUpdate(toolCalls);
expect(onConfirmSpy).not.toHaveBeenCalled();
const calls = (mockEventBus.publish as Mock).mock.calls;
// Search if ToolCallConfirmationEvent was published
const confEvent = calls.find(
(call) =>
call[0].metadata?.coderAgent?.kind ===
CoderAgentEvent.ToolCallConfirmationEvent,
);
expect(confEvent).toBeDefined();
});
});
});
describe('currentPromptId and promptCount', () => {
it('should correctly initialize and update promptId and promptCount', async () => {
const mockConfig = createMockConfig();
+177 -19
View File
@@ -5,8 +5,8 @@
*/
import {
type AgentLoopContext,
Scheduler,
CoreToolScheduler,
type GeminiClient,
GeminiEventType,
ToolConfirmationOutcome,
@@ -69,10 +69,37 @@ import type { PartUnion, Part as genAiPart } from '@google/genai';
type UnionKeys<T> = T extends T ? keyof T : never;
type ConfirmationType = ToolCallConfirmationDetails['type'];
const VALID_CONFIRMATION_TYPES: readonly ConfirmationType[] = [
'edit',
'exec',
'mcp',
'info',
'ask_user',
'exit_plan_mode',
] as const;
function isToolCallConfirmationDetails(
value: unknown,
): value is ToolCallConfirmationDetails {
if (
typeof value !== 'object' ||
value === null ||
!('onConfirm' in value) ||
typeof value.onConfirm !== 'function' ||
!('type' in value) ||
typeof value.type !== 'string'
) {
return false;
}
return (VALID_CONFIRMATION_TYPES as readonly string[]).includes(value.type);
}
export class Task {
id: string;
contextId: string;
scheduler: Scheduler;
scheduler: Scheduler | CoreToolScheduler;
config: Config;
geminiClient: GeminiClient;
pendingToolConfirmationDetails: Map<string, ToolCallConfirmationDetails>;
@@ -113,10 +140,13 @@ export class Task {
this.contextId = contextId;
this.config = config;
this.scheduler = this.setupEventDrivenScheduler();
if (this.config.isEventDrivenSchedulerEnabled()) {
this.scheduler = this.setupEventDrivenScheduler();
} else {
this.scheduler = this.createLegacyScheduler();
}
const loopContext: AgentLoopContext = this.config;
this.geminiClient = loopContext.geminiClient;
this.geminiClient = this.config.getGeminiClient();
this.pendingToolConfirmationDetails = new Map();
this.taskState = 'submitted';
this.eventBus = eventBus;
@@ -145,8 +175,7 @@ export class Task {
// process. This is not scoped to the individual task but reflects the global connection
// state managed within the @gemini-cli/core module.
async getMetadata(): Promise<TaskMetadata> {
const loopContext: AgentLoopContext = this.config;
const toolRegistry = loopContext.toolRegistry;
const toolRegistry = this.config.getToolRegistry();
const mcpServers = this.config.getMcpClientManager()?.getMcpServers() || {};
const serverStatuses = getAllMCPServerStatuses();
const servers = Object.keys(mcpServers).map((serverName) => ({
@@ -231,7 +260,11 @@ export class Task {
this.pendingToolCalls.clear();
this.pendingCorrelationIds.clear();
this.scheduler.cancelAll();
if (this.scheduler instanceof Scheduler) {
this.scheduler.cancelAll();
} else {
this.scheduler.cancelAll(new AbortController().signal);
}
// Reset the promise for any future operations, ensuring it's in a clean state.
this._resetToolCompletionPromise();
}
@@ -376,14 +409,140 @@ export class Task {
this.eventBus?.publish(artifactEvent);
}
private async _schedulerAllToolCallsComplete(
completedToolCalls: CompletedToolCall[],
): Promise<void> {
logger.info(
'[Task] All tool calls completed by scheduler (batch):',
completedToolCalls.map((tc) => tc.request.callId),
);
this.completedToolCalls.push(...completedToolCalls);
completedToolCalls.forEach((tc) => {
this._resolveToolCall(tc.request.callId);
});
}
private _schedulerToolCallsUpdate(toolCalls: ToolCall[]): void {
logger.info(
'[Task] Scheduler tool calls updated:',
toolCalls.map((tc) => `${tc.request.callId} (${tc.status})`),
);
// Update state and send continuous, non-final updates
toolCalls.forEach((tc) => {
const previousStatus = this.pendingToolCalls.get(tc.request.callId);
const hasChanged = previousStatus !== tc.status;
// Resolve tool call if it has reached a terminal state
if (['success', 'error', 'cancelled'].includes(tc.status)) {
this._resolveToolCall(tc.request.callId);
} else {
// This will update the map
this._registerToolCall(tc.request.callId, tc.status);
}
if (tc.status === 'awaiting_approval' && tc.confirmationDetails) {
const details = tc.confirmationDetails;
if (isToolCallConfirmationDetails(details)) {
this.pendingToolConfirmationDetails.set(tc.request.callId, details);
}
}
// Only send an update if the status has actually changed.
if (hasChanged) {
// Skip sending confirmation event if we are going to auto-approve it anyway
if (
tc.status === 'awaiting_approval' &&
tc.confirmationDetails &&
this.isYoloMatch
) {
logger.info(
`[Task] Skipping ToolCallConfirmationEvent for ${tc.request.callId} due to YOLO mode.`,
);
} else {
const coderAgentMessage: CoderAgentMessage =
tc.status === 'awaiting_approval'
? { kind: CoderAgentEvent.ToolCallConfirmationEvent }
: { kind: CoderAgentEvent.ToolCallUpdateEvent };
const message = this.toolStatusMessage(tc, this.id, this.contextId);
const event = this._createStatusUpdateEvent(
this.taskState,
coderAgentMessage,
message,
false, // Always false for these continuous updates
);
this.eventBus?.publish(event);
}
}
});
if (this.isYoloMatch) {
logger.info(
'[Task] ' +
(this.autoExecute ? '' : 'YOLO mode enabled. ') +
'Auto-approving all tool calls.',
);
toolCalls.forEach((tc: ToolCall) => {
if (tc.status === 'awaiting_approval' && tc.confirmationDetails) {
const details = tc.confirmationDetails;
if (isToolCallConfirmationDetails(details)) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
details.onConfirm(ToolConfirmationOutcome.ProceedOnce);
this.pendingToolConfirmationDetails.delete(tc.request.callId);
}
}
});
return;
}
const allPendingStatuses = Array.from(this.pendingToolCalls.values());
const isAwaitingApproval = allPendingStatuses.some(
(status) => status === 'awaiting_approval',
);
const isExecuting = allPendingStatuses.some(
(status) => status === 'executing',
);
// The turn is complete and requires user input if at least one tool
// is waiting for the user's decision, and no other tool is actively
// running in the background.
if (
isAwaitingApproval &&
!isExecuting &&
!this.skipFinalTrueAfterInlineEdit
) {
this.skipFinalTrueAfterInlineEdit = false;
// We don't need to send another message, just a final status update.
this.setTaskStateAndPublishUpdate(
'input-required',
{ kind: CoderAgentEvent.StateChangeEvent },
undefined,
undefined,
/*final*/ true,
);
}
}
private createLegacyScheduler(): CoreToolScheduler {
const scheduler = new CoreToolScheduler({
outputUpdateHandler: this._schedulerOutputUpdate.bind(this),
onAllToolCallsComplete: this._schedulerAllToolCallsComplete.bind(this),
onToolCallsUpdate: this._schedulerToolCallsUpdate.bind(this),
getPreferredEditor: () => DEFAULT_GUI_EDITOR,
config: this.config,
});
return scheduler;
}
private messageBusListener?: (message: ToolCallsUpdateMessage) => void;
private setupEventDrivenScheduler(): Scheduler {
const loopContext: AgentLoopContext = this.config;
const messageBus = loopContext.messageBus;
const messageBus = this.config.getMessageBus();
const scheduler = new Scheduler({
schedulerId: this.id,
context: this.config,
config: this.config,
messageBus,
getPreferredEditor: () => DEFAULT_GUI_EDITOR,
});
@@ -399,15 +558,15 @@ export class Task {
dispose(): void {
if (this.messageBusListener) {
const loopContext: AgentLoopContext = this.config;
loopContext.messageBus.unsubscribe(
MessageBusType.TOOL_CALLS_UPDATE,
this.messageBusListener,
);
this.config
.getMessageBus()
.unsubscribe(MessageBusType.TOOL_CALLS_UPDATE, this.messageBusListener);
this.messageBusListener = undefined;
}
this.scheduler.dispose();
if (this.scheduler instanceof Scheduler) {
this.scheduler.dispose();
}
}
private handleEventDrivenToolCallsUpdate(
@@ -954,8 +1113,7 @@ export class Task {
try {
if (correlationId) {
const loopContext: AgentLoopContext = this.config;
await loopContext.messageBus.publish({
await this.config.getMessageBus().publish({
type: MessageBusType.TOOL_CONFIRMATION_RESPONSE,
correlationId,
confirmed:
@@ -59,9 +59,6 @@ describe('a2a-server memory commands', () => {
} as unknown as ToolRegistry;
mockConfig = {
get toolRegistry() {
return mockToolRegistry;
},
getToolRegistry: vi.fn().mockReturnValue(mockToolRegistry),
} as unknown as Config;
@@ -171,6 +168,7 @@ describe('a2a-server memory commands', () => {
]);
expect(mockAddMemory).toHaveBeenCalledWith(fact);
expect(mockConfig.getToolRegistry).toHaveBeenCalled();
expect(mockToolRegistry.getTool).toHaveBeenCalledWith('save_memory');
expect(mockSaveMemoryTool.buildAndExecute).toHaveBeenCalledWith(
{ fact },
+1 -3
View File
@@ -15,7 +15,6 @@ import type {
CommandContext,
CommandExecutionResponse,
} from './types.js';
import type { AgentLoopContext } from '@google/gemini-cli-core';
const DEFAULT_SANITIZATION_CONFIG = {
allowedEnvironmentVariables: [],
@@ -96,8 +95,7 @@ export class AddMemoryCommand implements Command {
return { name: this.name, data: result.content };
}
const loopContext: AgentLoopContext = context.config;
const toolRegistry = loopContext.toolRegistry;
const toolRegistry = context.config.getToolRegistry();
const tool = toolRegistry.getTool(result.toolName);
if (tool) {
const abortController = new AbortController();
@@ -91,15 +91,6 @@ describe('loadConfig', () => {
expect(fetchAdminControlsOnce).not.toHaveBeenCalled();
});
it('should pass clientName as a2a-server to Config', async () => {
await loadConfig(mockSettings, mockExtensionLoader, taskId);
expect(Config).toHaveBeenCalledWith(
expect.objectContaining({
clientName: 'a2a-server',
}),
);
});
describe('when admin controls experiment is enabled', () => {
beforeEach(() => {
// We need to cast to any here to modify the mock implementation
+2 -1
View File
@@ -62,7 +62,6 @@ export async function loadConfig(
const configParams: ConfigParameters = {
sessionId: taskId,
clientName: 'a2a-server',
model: PREVIEW_GEMINI_MODEL,
embeddingModel: DEFAULT_GEMINI_EMBEDDING_MODEL,
sandbox: undefined, // Sandbox might not be relevant for a server-side agent
@@ -107,6 +106,8 @@ export async function loadConfig(
trustedFolder: true,
extensionLoader,
checkpointing,
enableEventDrivenScheduler:
settings.experimental?.enableEventDrivenScheduler ?? true,
interactive: !isHeadlessMode(),
enableInteractiveShell: !isHeadlessMode(),
ptyInfo: 'auto',
@@ -40,6 +40,9 @@ export interface Settings {
general?: {
previewFeatures?: boolean;
};
experimental?: {
enableEventDrivenScheduler?: boolean;
};
// Git-aware file filtering settings
fileFiltering?: {
+1 -6
View File
@@ -65,12 +65,7 @@ vi.mock('../utils/logger.js', () => ({
}));
let config: Config;
const getToolRegistrySpy = vi.fn().mockReturnValue({
getTool: vi.fn(),
getAllToolNames: vi.fn().mockReturnValue([]),
getAllTools: vi.fn().mockReturnValue([]),
getToolsByServer: vi.fn().mockReturnValue([]),
});
const getToolRegistrySpy = vi.fn().mockReturnValue(ApprovalMode.DEFAULT);
const getApprovalModeSpy = vi.fn();
const getShellExecutionConfigSpy = vi.fn();
const getExtensionsSpy = vi.fn();
+4 -24
View File
@@ -16,12 +16,10 @@ import {
DEFAULT_TRUNCATE_TOOL_OUTPUT_THRESHOLD,
GeminiClient,
HookSystem,
type MessageBus,
PolicyDecision,
tmpdir,
type Config,
type Storage,
type ToolRegistry,
} from '@google/gemini-cli-core';
import { createMockMessageBus } from '@google/gemini-cli-core/src/test-utils/mock-message-bus.js';
import { expect, vi } from 'vitest';
@@ -32,28 +30,6 @@ export function createMockConfig(
const tmpDir = tmpdir();
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const mockConfig = {
get config() {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return this as unknown as Config;
},
get toolRegistry() {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const config = this as unknown as Config;
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return config.getToolRegistry?.() as unknown as ToolRegistry;
},
get messageBus() {
return (
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
(this as unknown as Config).getMessageBus?.() as unknown as MessageBus
);
},
get geminiClient() {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const config = this as unknown as Config;
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return config.getGeminiClient?.() as unknown as GeminiClient;
},
getToolRegistry: vi.fn().mockReturnValue({
getTool: vi.fn(),
getAllToolNames: vi.fn().mockReturnValue([]),
@@ -88,6 +64,7 @@ export function createMockConfig(
getEmbeddingModel: vi.fn().mockReturnValue('text-embedding-004'),
getSessionId: vi.fn().mockReturnValue('test-session-id'),
getUserTier: vi.fn(),
isEventDrivenSchedulerEnabled: vi.fn().mockReturnValue(false),
getMessageBus: vi.fn(),
getPolicyEngine: vi.fn(),
getEnableExtensionReloading: vi.fn().mockReturnValue(false),
@@ -100,6 +77,9 @@ export function createMockConfig(
...overrides,
} as unknown as Config;
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
(mockConfig as unknown as { config: Config; promptId: string }).config =
mockConfig;
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
(mockConfig as unknown as { config: Config; promptId: string }).promptId =
'test-prompt-id';
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli",
"version": "0.35.0-nightly.20260311.657f19c1f",
"version": "0.34.0-nightly.20260310.4653b126f",
"description": "Gemini CLI",
"license": "Apache-2.0",
"repository": {
@@ -26,7 +26,7 @@
"dist"
],
"config": {
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.35.0-nightly.20260311.657f19c1f"
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.34.0-nightly.20260310.4653b126f"
},
"dependencies": {
"@agentclientprotocol/sdk": "^0.12.0",
+4 -6
View File
@@ -57,17 +57,15 @@ function hasMeta(obj: unknown): obj is { _meta?: Record<string, unknown> } {
return typeof obj === 'object' && obj !== null && '_meta' in obj;
}
import type { Content, Part, FunctionCall } from '@google/genai';
import {
SettingScope,
loadSettings,
type LoadedSettings,
} from '../config/settings.js';
import type { LoadedSettings } from '../config/settings.js';
import { SettingScope, loadSettings } from '../config/settings.js';
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import { z } from 'zod';
import { randomUUID } from 'node:crypto';
import { loadCliConfig, type CliArgs } from '../config/config.js';
import type { CliArgs } from '../config/config.js';
import { loadCliConfig } from '../config/config.js';
import { runExitCleanup } from '../utils/cleanup.js';
import { SessionSelector } from '../utils/sessionUtils.js';
+2 -1
View File
@@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { listExtensions, type Config } from '@google/gemini-cli-core';
import { listExtensions } from '@google/gemini-cli-core';
import { SettingScope } from '../../config/settings.js';
import {
ExtensionManager,
@@ -18,6 +18,7 @@ import type {
CommandContext,
CommandExecutionResponse,
} from './types.js';
import type { Config } from '@google/gemini-cli-core';
export class ExtensionsCommand implements Command {
readonly name = 'extensions';
@@ -17,10 +17,8 @@ import {
import { handleInstall, installCommand } from './install.js';
import yargs from 'yargs';
import * as core from '@google/gemini-cli-core';
import {
ExtensionManager,
type inferInstallMetadata,
} from '../../config/extension-manager.js';
import type { inferInstallMetadata } from '../../config/extension-manager.js';
import { ExtensionManager } from '../../config/extension-manager.js';
import type {
promptForConsentNonInteractive,
requestConsentNonInteractive,
+2 -1
View File
@@ -6,7 +6,8 @@
import { describe, it, expect, vi } from 'vitest';
import { mcpCommand } from './mcp.js';
import yargs, { type Argv } from 'yargs';
import { type Argv } from 'yargs';
import yargs from 'yargs';
describe('mcp command', () => {
it('should have correct command definition', () => {
+13 -13
View File
@@ -71,7 +71,7 @@ describe('mcp add command', () => {
});
});
it('should add a stdio server to project settings', async () => {
it('should add a stdio server to workspace settings', async () => {
await parser.parseAsync(
'add -e FOO=bar my-server /path/to/server arg1 arg2',
);
@@ -121,7 +121,7 @@ describe('mcp add command', () => {
});
});
it('should add an http server to project settings', async () => {
it('should add an http server to workspace settings', async () => {
await parser.parseAsync(
'add --transport http -H "Authorization: Bearer your-token" http-server https://example.com/mcp',
);
@@ -219,12 +219,12 @@ describe('mcp add command', () => {
});
};
describe('when in a project directory', () => {
describe('when in a workspace directory', () => {
beforeEach(() => {
setupMocks('/path/to/project', '/path/to/project');
});
it('should use project scope by default', async () => {
it('should use workspace scope by default', async () => {
await parser.parseAsync(`add ${serverName} ${command}`);
expect(mockSetValue).toHaveBeenCalledWith(
SettingScope.Workspace,
@@ -233,7 +233,7 @@ describe('mcp add command', () => {
);
});
it('should use project scope when --scope=project is used', async () => {
it('should support --scope=project as a compatibility alias', async () => {
await parser.parseAsync(`add --scope project ${serverName} ${command}`);
expect(mockSetValue).toHaveBeenCalledWith(
SettingScope.Workspace,
@@ -252,12 +252,12 @@ describe('mcp add command', () => {
});
});
describe('when in a subdirectory of a project', () => {
describe('when in a subdirectory of a workspace', () => {
beforeEach(() => {
setupMocks('/path/to/project/subdir', '/path/to/project');
});
it('should use project scope by default', async () => {
it('should use workspace scope by default', async () => {
await parser.parseAsync(`add ${serverName} ${command}`);
expect(mockSetValue).toHaveBeenCalledWith(
SettingScope.Workspace,
@@ -290,7 +290,7 @@ describe('mcp add command', () => {
expect(mockSetValue).not.toHaveBeenCalled();
});
it('should show an error when --scope=project is used explicitly', async () => {
it('should show an error when --scope=project is used as a compatibility alias', async () => {
const mockProcessExit = vi
.spyOn(process, 'exit')
.mockImplementation((() => {
@@ -319,12 +319,12 @@ describe('mcp add command', () => {
});
});
describe('when in a subdirectory of home (not a project)', () => {
describe('when in a subdirectory of home (not a workspace)', () => {
beforeEach(() => {
setupMocks('/home/user/some/dir', '/home/user/some/dir');
});
it('should use project scope by default', async () => {
it('should use workspace scope by default', async () => {
await parser.parseAsync(`add ${serverName} ${command}`);
expect(mockSetValue).toHaveBeenCalledWith(
SettingScope.Workspace,
@@ -347,12 +347,12 @@ describe('mcp add command', () => {
});
});
describe('when outside of home (not a project)', () => {
describe('when outside of home (not a workspace)', () => {
beforeEach(() => {
setupMocks('/tmp/foo', '/tmp/foo');
});
it('should use project scope by default', async () => {
it('should use workspace scope by default', async () => {
await parser.parseAsync(`add ${serverName} ${command}`);
expect(mockSetValue).toHaveBeenCalledWith(
SettingScope.Workspace,
@@ -386,7 +386,7 @@ describe('mcp add command', () => {
});
});
it('should update the existing server in the project scope', async () => {
it('should update the existing server in the workspace scope', async () => {
await parser.parseAsync(
`add ${serverName} ${updatedCommand} ${updatedArgs.join(' ')}`,
);
+11 -7
View File
@@ -38,10 +38,12 @@ async function addMcpServer(
excludeTools,
} = options;
const displayScope = scope === 'user' ? 'user' : 'workspace';
const settings = loadSettings(process.cwd());
const inHome = settings.workspace.path === settings.user.path;
if (scope === 'project' && inHome) {
if (displayScope === 'workspace' && inHome) {
debugLogger.error(
'Error: Please use --scope user to edit settings in the home directory.',
);
@@ -120,7 +122,7 @@ async function addMcpServer(
const isExistingServer = !!mcpServers[name];
if (isExistingServer) {
debugLogger.log(
`MCP server "${name}" is already configured within ${scope} settings.`,
`MCP server "${name}" is already configured within ${displayScope} settings.`,
);
}
@@ -129,10 +131,12 @@ async function addMcpServer(
settings.setValue(settingsScope, 'mcpServers', mcpServers);
if (isExistingServer) {
debugLogger.log(`MCP server "${name}" updated in ${scope} settings.`);
debugLogger.log(
`MCP server "${name}" updated in ${displayScope} settings.`,
);
} else {
debugLogger.log(
`MCP server "${name}" added to ${scope} settings. (${transport})`,
`MCP server "${name}" added to ${displayScope} settings. (${transport})`,
);
}
}
@@ -159,10 +163,10 @@ export const addCommand: CommandModule = {
})
.option('scope', {
alias: 's',
describe: 'Configuration scope (user or project)',
describe: 'Configuration scope (user or workspace)',
type: 'string',
default: 'project',
choices: ['user', 'project'],
default: 'workspace',
choices: ['user', 'workspace', 'project'],
})
.option('transport', {
alias: ['t', 'type'],
+3 -3
View File
@@ -81,7 +81,7 @@ describe('mcp remove command', () => {
parser = yargsInstance;
});
it('should remove a server from project settings', async () => {
it('should remove a server from workspace settings', async () => {
await parser.parseAsync('remove test-server');
expect(mockSetValue).toHaveBeenCalledWith(
@@ -99,7 +99,7 @@ describe('mcp remove command', () => {
expect(mockSetValue).not.toHaveBeenCalled();
expect(debugLogSpy).toHaveBeenCalledWith(
'Server "non-existent-server" not found in project settings.',
'Server "non-existent-server" not found in workspace settings.',
);
debugLogSpy.mockRestore();
});
@@ -159,7 +159,7 @@ describe('mcp remove command', () => {
expect(updatedContent).not.toContain('"server-to-remove"');
expect(debugLogSpy).toHaveBeenCalledWith(
'Server "server-to-remove" removed from project settings.',
'Server "server-to-remove" removed from workspace settings.',
);
debugLogSpy.mockRestore();
+6 -5
View File
@@ -17,6 +17,7 @@ async function removeMcpServer(
},
) {
const { scope } = options;
const displayScope = scope === 'user' ? 'user' : 'workspace';
const settingsScope =
scope === 'user' ? SettingScope.User : SettingScope.Workspace;
const settings = loadSettings();
@@ -25,7 +26,7 @@ async function removeMcpServer(
const mcpServers = existingSettings.mcpServers || {};
if (!mcpServers[name]) {
debugLogger.log(`Server "${name}" not found in ${scope} settings.`);
debugLogger.log(`Server "${name}" not found in ${displayScope} settings.`);
return;
}
@@ -33,7 +34,7 @@ async function removeMcpServer(
settings.setValue(settingsScope, 'mcpServers', mcpServers);
debugLogger.log(`Server "${name}" removed from ${scope} settings.`);
debugLogger.log(`Server "${name}" removed from ${displayScope} settings.`);
}
export const removeCommand: CommandModule = {
@@ -49,10 +50,10 @@ export const removeCommand: CommandModule = {
})
.option('scope', {
alias: 's',
describe: 'Configuration scope (user or project)',
describe: 'Configuration scope (user or workspace)',
type: 'string',
default: 'project',
choices: ['user', 'project'],
default: 'workspace',
choices: ['user', 'workspace', 'project'],
}),
handler: async (argv) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
@@ -5,10 +5,11 @@
*/
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
import { coreEvents, type Config } from '@google/gemini-cli-core';
import { coreEvents } from '@google/gemini-cli-core';
import { handleList, listCommand } from './list.js';
import { loadSettings, type LoadedSettings } from '../../config/settings.js';
import { loadCliConfig } from '../../config/config.js';
import type { Config } from '@google/gemini-cli-core';
import chalk from 'chalk';
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
+4 -59
View File
@@ -1773,7 +1773,7 @@ describe('loadCliConfig model selection', () => {
});
it('always prefers model from argv', async () => {
process.argv = ['node', 'script.js', '--model', 'gemini-2.5-flash'];
process.argv = ['node', 'script.js', '--model', 'gemini-2.5-flash-preview'];
const argv = await parseArguments(createTestMergedSettings());
const config = await loadCliConfig(
createTestMergedSettings({
@@ -1785,11 +1785,11 @@ describe('loadCliConfig model selection', () => {
argv,
);
expect(config.getModel()).toBe('gemini-2.5-flash');
expect(config.getModel()).toBe('gemini-2.5-flash-preview');
});
it('selects the model from argv if provided', async () => {
process.argv = ['node', 'script.js', '--model', 'gemini-2.5-flash'];
process.argv = ['node', 'script.js', '--model', 'gemini-2.5-flash-preview'];
const argv = await parseArguments(createTestMergedSettings());
const config = await loadCliConfig(
createTestMergedSettings({
@@ -1799,7 +1799,7 @@ describe('loadCliConfig model selection', () => {
argv,
);
expect(config.getModel()).toBe('gemini-2.5-flash');
expect(config.getModel()).toBe('gemini-2.5-flash-preview');
});
it('selects the default auto model if provided via auto alias', async () => {
@@ -3616,58 +3616,3 @@ describe('loadCliConfig mcpEnabled', () => {
});
});
});
describe('loadCliConfig acpMode and clientName', () => {
beforeEach(() => {
vi.resetAllMocks();
vi.mocked(os.homedir).mockReturnValue('/mock/home/user');
vi.stubEnv('GEMINI_API_KEY', 'test-api-key');
vi.spyOn(ExtensionManager.prototype, 'getExtensions').mockReturnValue([]);
});
afterEach(() => {
vi.unstubAllEnvs();
});
it('should set acpMode to true and detect clientName when --acp flag is used', async () => {
process.argv = ['node', 'script.js', '--acp'];
vi.stubEnv('TERM_PROGRAM', 'vscode');
vi.stubEnv('VSCODE_GIT_ASKPASS_MAIN', '');
vi.stubEnv('ANTIGRAVITY_CLI_ALIAS', '');
const argv = await parseArguments(createTestMergedSettings());
const config = await loadCliConfig(
createTestMergedSettings(),
'test-session',
argv,
);
expect(config.getAcpMode()).toBe(true);
expect(config.getClientName()).toBe('acp-vscode');
});
it('should set acpMode to true but leave clientName undefined for generic terminals', async () => {
process.argv = ['node', 'script.js', '--acp'];
vi.stubEnv('TERM_PROGRAM', 'iTerm.app'); // Generic terminal
vi.stubEnv('VSCODE_GIT_ASKPASS_MAIN', '');
vi.stubEnv('ANTIGRAVITY_CLI_ALIAS', '');
const argv = await parseArguments(createTestMergedSettings());
const config = await loadCliConfig(
createTestMergedSettings(),
'test-session',
argv,
);
expect(config.getAcpMode()).toBe(true);
expect(config.getClientName()).toBeUndefined();
});
it('should set acpMode to false and clientName to undefined by default', async () => {
process.argv = ['node', 'script.js'];
const argv = await parseArguments(createTestMergedSettings());
const config = await loadCliConfig(
createTestMergedSettings(),
'test-session',
argv,
);
expect(config.getAcpMode()).toBe(false);
expect(config.getClientName()).toBeUndefined();
});
});
+26 -61
View File
@@ -31,8 +31,6 @@ import {
type HierarchicalMemory,
coreEvents,
GEMINI_MODEL_ALIAS_AUTO,
isValidModelOrAlias,
getValidModelsAndAliases,
getAdminErrorMessage,
isHeadlessMode,
Config,
@@ -42,7 +40,6 @@ import {
type HookDefinition,
type HookEventName,
type OutputFormat,
detectIdeFromEnv,
} from '@google/gemini-cli-core';
import {
type Settings,
@@ -79,7 +76,6 @@ export interface CliArgs {
yolo: boolean | undefined;
approvalMode: string | undefined;
policy: string[] | undefined;
adminPolicy: string[] | undefined;
allowedMcpServerNames: string[] | undefined;
allowedTools: string[] | undefined;
acp?: boolean;
@@ -101,21 +97,6 @@ export interface CliArgs {
isCommand: boolean | undefined;
}
/**
* Helper to coerce comma-separated or multiple flag values into a flat array.
*/
const coerceCommaSeparated = (values: string[]): string[] => {
if (values.length === 1 && values[0] === '') {
return [''];
}
return values.flatMap((v) =>
v
.split(',')
.map((s) => s.trim())
.filter(Boolean),
);
};
export async function parseArguments(
settings: MergedSettings,
): Promise<CliArgs> {
@@ -185,15 +166,14 @@ export async function parseArguments(
nargs: 1,
description:
'Additional policy files or directories to load (comma-separated or multiple --policy)',
coerce: coerceCommaSeparated,
})
.option('admin-policy', {
type: 'array',
string: true,
nargs: 1,
description:
'Additional admin policy files or directories to load (comma-separated or multiple --admin-policy)',
coerce: coerceCommaSeparated,
coerce: (policies: string[]) =>
// Handle comma-separated values
policies.flatMap((p) =>
p
.split(',')
.map((s) => s.trim())
.filter(Boolean),
),
})
.option('acp', {
type: 'boolean',
@@ -209,7 +189,11 @@ export async function parseArguments(
string: true,
nargs: 1,
description: 'Allowed MCP server names',
coerce: coerceCommaSeparated,
coerce: (mcpServerNames: string[]) =>
// Handle comma-separated values
mcpServerNames.flatMap((mcpServerName) =>
mcpServerName.split(',').map((m) => m.trim()),
),
})
.option('allowed-tools', {
type: 'array',
@@ -217,7 +201,9 @@ export async function parseArguments(
nargs: 1,
description:
'[DEPRECATED: Use Policy Engine instead See https://geminicli.com/docs/core/policy-engine] Tools that are allowed to run without confirmation',
coerce: coerceCommaSeparated,
coerce: (tools: string[]) =>
// Handle comma-separated values
tools.flatMap((tool) => tool.split(',').map((t) => t.trim())),
})
.option('extensions', {
alias: 'e',
@@ -226,7 +212,11 @@ export async function parseArguments(
nargs: 1,
description:
'A list of extensions to use. If not provided, all extensions are used.',
coerce: coerceCommaSeparated,
coerce: (extensions: string[]) =>
// Handle comma-separated values
extensions.flatMap((extension) =>
extension.split(',').map((e) => e.trim()),
),
})
.option('list-extensions', {
alias: 'l',
@@ -268,7 +258,9 @@ export async function parseArguments(
nargs: 1,
description:
'Additional directories to include in the workspace (comma-separated or multiple --include-directories)',
coerce: coerceCommaSeparated,
coerce: (dirs: string[]) =>
// Handle comma-separated values
dirs.flatMap((dir) => dir.split(',').map((d) => d.trim())),
})
.option('screen-reader', {
type: 'boolean',
@@ -651,8 +643,7 @@ export async function loadCliConfig(
...settings.mcp,
allowed: argv.allowedMcpServerNames ?? settings.mcp?.allowed,
},
policyPaths: argv.policy ?? settings.policyPaths,
adminPolicyPaths: argv.adminPolicy ?? settings.adminPolicyPaths,
policyPaths: argv.policy,
};
const { workspacePoliciesDir, policyUpdateConfirmationRequest } =
@@ -673,18 +664,6 @@ export async function loadCliConfig(
const specifiedModel =
argv.model || process.env['GEMINI_MODEL'] || settings.model?.name;
// Validate the model if one was explicitly specified
if (specifiedModel && specifiedModel !== GEMINI_MODEL_ALIAS_AUTO) {
if (!isValidModelOrAlias(specifiedModel)) {
const validModels = getValidModelsAndAliases();
throw new FatalConfigError(
`Invalid model: "${specifiedModel}"\n\n` +
`Valid models and aliases:\n${validModels.map((m) => ` - ${m}`).join('\n')}\n\n` +
`Use /model to switch models interactively.`,
);
}
}
const resolvedModel =
specifiedModel === GEMINI_MODEL_ALIAS_AUTO
? defaultModel
@@ -725,21 +704,8 @@ export async function loadCliConfig(
}
}
const isAcpMode = !!argv.acp || !!argv.experimentalAcp;
let clientName: string | undefined = undefined;
if (isAcpMode) {
const ide = detectIdeFromEnv();
if (
ide &&
(ide.name !== 'vscode' || process.env['TERM_PROGRAM'] === 'vscode')
) {
clientName = `acp-${ide.name}`;
}
}
return new Config({
acpMode: isAcpMode,
clientName,
acpMode: !!argv.acp || !!argv.experimentalAcp,
sessionId,
clientVersion: await getVersion(),
embeddingModel: DEFAULT_GEMINI_EMBEDDING_MODEL,
@@ -857,7 +823,6 @@ export async function loadCliConfig(
disableLLMCorrection: settings.tools?.disableLLMCorrection,
rawOutput: argv.rawOutput,
acceptRawOutputRisk: argv.acceptRawOutputRisk,
dynamicModelConfiguration: settings.experimental?.dynamicModelConfiguration,
modelConfigServiceConfig: settings.modelConfigs,
// TODO: loading of hooks based on workspace trust
enableHooks: settings.hooksConfig.enabled,
@@ -9,16 +9,16 @@ import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { ExtensionManager } from './extension-manager.js';
import { createTestMergedSettings, type MergedSettings } from './settings.js';
import { createTestMergedSettings } from './settings.js';
import { createExtension } from '../test-utils/createExtension.js';
import { EXTENSIONS_DIRECTORY_NAME } from './extensions/variables.js';
import { themeManager } from '../ui/themes/theme-manager.js';
import {
TrustLevel,
loadTrustedFolders,
isWorkspaceTrusted,
} from './trustedFolders.js';
import { getRealPath, type CustomTheme } from '@google/gemini-cli-core';
import { getRealPath } from '@google/gemini-cli-core';
import type { MergedSettings } from './settings.js';
const mockHomedir = vi.hoisted(() => vi.fn(() => '/tmp/mock-home'));
@@ -39,26 +39,6 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
};
});
const testTheme: CustomTheme = {
type: 'custom',
name: 'MyTheme',
background: {
primary: '#282828',
diff: { added: '#2b3312', removed: '#341212' },
},
text: {
primary: '#ebdbb2',
secondary: '#a89984',
link: '#83a598',
accent: '#d3869b',
},
status: {
success: '#b8bb26',
warning: '#fabd2f',
error: '#fb4934',
},
};
describe('ExtensionManager', () => {
let tempHomeDir: string;
let tempWorkspaceDir: string;
@@ -86,7 +66,6 @@ describe('ExtensionManager', () => {
});
afterEach(() => {
themeManager.clearExtensionThemes();
try {
fs.rmSync(tempHomeDir, { recursive: true, force: true });
} catch (_e) {
@@ -506,45 +485,4 @@ describe('ExtensionManager', () => {
).rejects.toThrow(/already installed/);
});
});
describe('early theme registration', () => {
it('should register themes with ThemeManager during loadExtensions for active extensions', async () => {
createExtension({
extensionsDir: userExtensionsDir,
name: 'themed-ext',
version: '1.0.0',
themes: [testTheme],
});
await extensionManager.loadExtensions();
expect(themeManager.getCustomThemeNames()).toContain(
'MyTheme (themed-ext)',
);
});
it('should not register themes for inactive extensions', async () => {
createExtension({
extensionsDir: userExtensionsDir,
name: 'disabled-ext',
version: '1.0.0',
themes: [testTheme],
});
// Disable the extension by creating an enablement override
const manager = new ExtensionManager({
enabledExtensionOverrides: ['none'],
settings: createTestMergedSettings(),
workspaceDir: tempWorkspaceDir,
requestConsent: vi.fn().mockResolvedValue(true),
requestSetting: null,
});
await manager.loadExtensions();
expect(themeManager.getCustomThemeNames()).not.toContain(
'MyTheme (disabled-ext)',
);
});
});
});
+1 -8
View File
@@ -564,7 +564,7 @@ Would you like to attempt to install via "git clone" instead?`,
protected override async startExtension(extension: GeminiCLIExtension) {
await super.startExtension(extension);
if (extension.themes && !themeManager.hasExtensionThemes(extension.name)) {
if (extension.themes) {
themeManager.registerExtensionThemes(extension.name, extension.themes);
}
}
@@ -624,13 +624,6 @@ Would you like to attempt to install via "git clone" instead?`,
this.loadedExtensions = builtExtensions;
// Register extension themes early so they're available at startup.
for (const ext of this.loadedExtensions) {
if (ext.isActive && ext.themes) {
themeManager.registerExtensionThemes(ext.name, ext.themes);
}
}
await Promise.all(
this.loadedExtensions.map((ext) => this.maybeStartExtension(ext)),
);
@@ -15,10 +15,11 @@ import {
type ExtensionUpdateStatus,
} from '../../ui/state/extensions.js';
import { ExtensionStorage } from './storage.js';
import { copyExtension, type ExtensionManager } from '../extension-manager.js';
import { copyExtension } from '../extension-manager.js';
import { checkForExtensionUpdate } from './github.js';
import { loadInstallMetadata } from '../extension.js';
import * as fs from 'node:fs';
import type { ExtensionManager } from '../extension-manager.js';
import type { GeminiCLIExtension } from '@google/gemini-cli-core';
// Mock dependencies
+2 -2
View File
@@ -9,8 +9,8 @@ import type { MergedSettings } from './settings.js';
export const ALL_ITEMS = [
{
id: 'workspace',
header: 'workspace (/directory)',
description: 'Current working directory',
header: 'workspace',
description: 'Current workspace path',
},
{
id: 'git-branch',
-1
View File
@@ -61,7 +61,6 @@ export async function createPolicyEngineConfig(
tools: settings.tools,
mcpServers: settings.mcpServers,
policyPaths: settings.policyPaths,
adminPolicyPaths: settings.adminPolicyPaths,
workspacePoliciesDir,
};
+13 -180
View File
@@ -90,13 +90,7 @@ describe('loadSandboxConfig', () => {
process.env['GEMINI_SANDBOX'] = 'docker';
mockedCommandExistsSync.mockReturnValue(true);
const config = await loadSandboxConfig({}, {});
expect(config).toEqual({
enabled: true,
allowedPaths: [],
networkAccess: false,
command: 'docker',
image: 'default/image',
});
expect(config).toEqual({ command: 'docker', image: 'default/image' });
expect(mockedCommandExistsSync).toHaveBeenCalledWith('docker');
});
@@ -119,13 +113,7 @@ describe('loadSandboxConfig', () => {
process.env['GEMINI_SANDBOX'] = 'lxc';
mockedCommandExistsSync.mockReturnValue(true);
const config = await loadSandboxConfig({}, {});
expect(config).toEqual({
enabled: true,
allowedPaths: [],
networkAccess: false,
command: 'lxc',
image: 'default/image',
});
expect(config).toEqual({ command: 'lxc', image: 'default/image' });
expect(mockedCommandExistsSync).toHaveBeenCalledWith('lxc');
});
@@ -146,9 +134,6 @@ describe('loadSandboxConfig', () => {
);
const config = await loadSandboxConfig({}, { sandbox: true });
expect(config).toEqual({
enabled: true,
allowedPaths: [],
networkAccess: false,
command: 'sandbox-exec',
image: 'default/image',
});
@@ -159,9 +144,6 @@ describe('loadSandboxConfig', () => {
mockedCommandExistsSync.mockReturnValue(true); // all commands exist
const config = await loadSandboxConfig({}, { sandbox: true });
expect(config).toEqual({
enabled: true,
allowedPaths: [],
networkAccess: false,
command: 'sandbox-exec',
image: 'default/image',
});
@@ -171,26 +153,14 @@ describe('loadSandboxConfig', () => {
mockedOsPlatform.mockReturnValue('linux');
mockedCommandExistsSync.mockImplementation((cmd) => cmd === 'docker');
const config = await loadSandboxConfig({ tools: { sandbox: true } }, {});
expect(config).toEqual({
enabled: true,
allowedPaths: [],
networkAccess: false,
command: 'docker',
image: 'default/image',
});
expect(config).toEqual({ command: 'docker', image: 'default/image' });
});
it('should use podman if available and docker is not', async () => {
mockedOsPlatform.mockReturnValue('linux');
mockedCommandExistsSync.mockImplementation((cmd) => cmd === 'podman');
const config = await loadSandboxConfig({}, { sandbox: true });
expect(config).toEqual({
enabled: true,
allowedPaths: [],
networkAccess: false,
command: 'podman',
image: 'default/image',
});
expect(config).toEqual({ command: 'podman', image: 'default/image' });
});
it('should throw if sandbox: true but no command is found', async () => {
@@ -207,13 +177,7 @@ describe('loadSandboxConfig', () => {
it('should use the specified command if it exists', async () => {
mockedCommandExistsSync.mockReturnValue(true);
const config = await loadSandboxConfig({}, { sandbox: 'podman' });
expect(config).toEqual({
enabled: true,
allowedPaths: [],
networkAccess: false,
command: 'podman',
image: 'default/image',
});
expect(config).toEqual({ command: 'podman', image: 'default/image' });
expect(mockedCommandExistsSync).toHaveBeenCalledWith('podman');
});
@@ -241,26 +205,14 @@ describe('loadSandboxConfig', () => {
process.env['GEMINI_SANDBOX'] = 'docker';
mockedCommandExistsSync.mockReturnValue(true);
const config = await loadSandboxConfig({}, {});
expect(config).toEqual({
enabled: true,
allowedPaths: [],
networkAccess: false,
command: 'docker',
image: 'env/image',
});
expect(config).toEqual({ command: 'docker', image: 'env/image' });
});
it('should use image from package.json if env var is not set', async () => {
process.env['GEMINI_SANDBOX'] = 'docker';
mockedCommandExistsSync.mockReturnValue(true);
const config = await loadSandboxConfig({}, {});
expect(config).toEqual({
enabled: true,
allowedPaths: [],
networkAccess: false,
command: 'docker',
image: 'default/image',
});
expect(config).toEqual({ command: 'docker', image: 'default/image' });
});
it('should return undefined if command is found but no image is configured', async () => {
@@ -282,115 +234,20 @@ describe('loadSandboxConfig', () => {
'should enable sandbox for value: %s',
async (value) => {
const config = await loadSandboxConfig({}, { sandbox: value });
expect(config).toEqual({
enabled: true,
allowedPaths: [],
networkAccess: false,
command: 'docker',
image: 'default/image',
});
expect(config).toEqual({ command: 'docker', image: 'default/image' });
},
);
it.each([false, 'false', '0', undefined, null, ''])(
'should disable sandbox for value: %s',
async (value) => {
// `null` is not a valid type for the arg, but good to test falsiness
// \`null\` is not a valid type for the arg, but good to test falsiness
const config = await loadSandboxConfig({}, { sandbox: value });
expect(config).toBeUndefined();
},
);
});
describe('with SandboxConfig object in settings', () => {
beforeEach(() => {
mockedOsPlatform.mockReturnValue('linux');
mockedCommandExistsSync.mockImplementation((cmd) => cmd === 'docker');
});
it('should support object structure with enabled: true', async () => {
const config = await loadSandboxConfig(
{
tools: {
sandbox: {
enabled: true,
allowedPaths: ['/tmp'],
networkAccess: true,
},
},
},
{},
);
expect(config).toEqual({
enabled: true,
allowedPaths: ['/tmp'],
networkAccess: true,
command: 'docker',
image: 'default/image',
});
});
it('should support object structure with explicit command', async () => {
mockedCommandExistsSync.mockImplementation((cmd) => cmd === 'podman');
const config = await loadSandboxConfig(
{
tools: {
sandbox: {
enabled: true,
command: 'podman',
},
},
},
{},
);
expect(config?.command).toBe('podman');
});
it('should support object structure with custom image', async () => {
const config = await loadSandboxConfig(
{
tools: {
sandbox: {
enabled: true,
image: 'custom/image',
},
},
},
{},
);
expect(config?.image).toBe('custom/image');
});
it('should return undefined if enabled is false in object', async () => {
const config = await loadSandboxConfig(
{
tools: {
sandbox: {
enabled: false,
},
},
},
{},
);
expect(config).toBeUndefined();
});
it('should prioritize CLI flag over settings object', async () => {
const config = await loadSandboxConfig(
{
tools: {
sandbox: {
enabled: true,
allowedPaths: ['/settings-path'],
},
},
},
{ sandbox: false },
);
expect(config).toBeUndefined();
});
});
describe('with sandbox: runsc (gVisor)', () => {
beforeEach(() => {
mockedOsPlatform.mockReturnValue('linux');
@@ -400,13 +257,7 @@ describe('loadSandboxConfig', () => {
it('should use runsc via CLI argument on Linux', async () => {
const config = await loadSandboxConfig({}, { sandbox: 'runsc' });
expect(config).toEqual({
enabled: true,
allowedPaths: [],
networkAccess: false,
command: 'runsc',
image: 'default/image',
});
expect(config).toEqual({ command: 'runsc', image: 'default/image' });
expect(mockedCommandExistsSync).toHaveBeenCalledWith('runsc');
expect(mockedCommandExistsSync).toHaveBeenCalledWith('docker');
});
@@ -415,13 +266,7 @@ describe('loadSandboxConfig', () => {
process.env['GEMINI_SANDBOX'] = 'runsc';
const config = await loadSandboxConfig({}, {});
expect(config).toEqual({
enabled: true,
allowedPaths: [],
networkAccess: false,
command: 'runsc',
image: 'default/image',
});
expect(config).toEqual({ command: 'runsc', image: 'default/image' });
expect(mockedCommandExistsSync).toHaveBeenCalledWith('runsc');
expect(mockedCommandExistsSync).toHaveBeenCalledWith('docker');
});
@@ -432,13 +277,7 @@ describe('loadSandboxConfig', () => {
{},
);
expect(config).toEqual({
enabled: true,
allowedPaths: [],
networkAccess: false,
command: 'runsc',
image: 'default/image',
});
expect(config).toEqual({ command: 'runsc', image: 'default/image' });
expect(mockedCommandExistsSync).toHaveBeenCalledWith('runsc');
expect(mockedCommandExistsSync).toHaveBeenCalledWith('docker');
});
@@ -450,13 +289,7 @@ describe('loadSandboxConfig', () => {
{ sandbox: 'podman' },
);
expect(config).toEqual({
enabled: true,
allowedPaths: [],
networkAccess: false,
command: 'runsc',
image: 'default/image',
});
expect(config).toEqual({ command: 'runsc', image: 'default/image' });
});
it('should reject runsc on macOS (Linux-only)', async () => {
+5 -30
View File
@@ -23,7 +23,7 @@ const __dirname = path.dirname(__filename);
interface SandboxCliArgs {
sandbox?: boolean | string | null;
}
const VALID_SANDBOX_COMMANDS = [
const VALID_SANDBOX_COMMANDS: ReadonlyArray<SandboxConfig['command']> = [
'docker',
'podman',
'sandbox-exec',
@@ -31,10 +31,8 @@ const VALID_SANDBOX_COMMANDS = [
'lxc',
];
function isSandboxCommand(
value: string,
): value is Exclude<SandboxConfig['command'], undefined> {
return VALID_SANDBOX_COMMANDS.includes(value);
function isSandboxCommand(value: string): value is SandboxConfig['command'] {
return (VALID_SANDBOX_COMMANDS as readonly string[]).includes(value);
}
function getSandboxCommand(
@@ -118,36 +116,13 @@ export async function loadSandboxConfig(
argv: SandboxCliArgs,
): Promise<SandboxConfig | undefined> {
const sandboxOption = argv.sandbox ?? settings.tools?.sandbox;
let sandboxValue: boolean | string | null | undefined;
let allowedPaths: string[] = [];
let networkAccess = false;
let customImage: string | undefined;
if (
typeof sandboxOption === 'object' &&
sandboxOption !== null &&
!Array.isArray(sandboxOption)
) {
const config = sandboxOption;
sandboxValue = config.enabled ? (config.command ?? true) : false;
allowedPaths = config.allowedPaths ?? [];
networkAccess = config.networkAccess ?? false;
customImage = config.image;
} else if (typeof sandboxOption !== 'object' || sandboxOption === null) {
sandboxValue = sandboxOption;
}
const command = getSandboxCommand(sandboxValue);
const command = getSandboxCommand(sandboxOption);
const packageJson = await getPackageJson(__dirname);
const image =
process.env['GEMINI_SANDBOX_IMAGE'] ??
process.env['GEMINI_SANDBOX_IMAGE_DEFAULT'] ??
customImage ??
packageJson?.config?.sandboxImageUri;
return command && image
? { enabled: true, allowedPaths, networkAccess, command, image }
: undefined;
return command && image ? { command, image } : undefined;
}
+23 -137
View File
@@ -18,7 +18,6 @@ import {
type AuthType,
type AgentOverride,
type CustomTheme,
type SandboxConfig,
} from '@google/gemini-cli-core';
import type { SessionRetentionSettings } from './settings.js';
import { DEFAULT_MIN_RETENTION } from '../utils/sessionCleanup.js';
@@ -135,18 +134,6 @@ export interface SettingsSchema {
export type MemoryImportFormat = 'tree' | 'flat';
export type DnsResolutionOrder = 'ipv4first' | 'verbatim';
const pathArraySetting = (label: string, description: string) => ({
type: 'array' as const,
label,
category: 'Advanced' as const,
requiresRestart: true as const,
default: [] as string[],
description,
showInDialog: false as const,
items: { type: 'string' as const },
mergeStrategy: MergeStrategy.UNION,
});
/**
* The canonical schema for all settings.
* The structure of this object defines the structure of the `Settings` type.
@@ -169,15 +156,17 @@ const SETTINGS_SCHEMA = {
},
},
policyPaths: pathArraySetting(
'Policy Paths',
'Additional policy files or directories to load.',
),
adminPolicyPaths: pathArraySetting(
'Admin Policy Paths',
'Additional admin policy files or directories to load.',
),
policyPaths: {
type: 'array',
label: 'Policy Paths',
category: 'Advanced',
requiresRestart: true,
default: [] as string[],
description: 'Additional policy files or directories to load.',
showInDialog: false,
items: { type: 'string' },
mergeStrategy: MergeStrategy.UNION,
},
general: {
type: 'object',
@@ -215,8 +204,7 @@ const SETTINGS_SCHEMA = {
description: oneLine`
The default approval mode for tool execution.
'default' prompts for approval, 'auto_edit' auto-approves edit tools,
and 'plan' is read-only mode. YOLO mode (auto-approve all actions) can
only be enabled via command line (--yolo or --approval-mode=yolo).
and 'plan' is read-only mode. 'yolo' is not supported yet.
`,
showInDialog: true,
options: [
@@ -318,10 +306,10 @@ const SETTINGS_SCHEMA = {
label: 'Retry Fetch Errors',
category: 'General',
requiresRestart: false,
default: true,
default: false,
description:
'Retry on "exception TypeError: fetch failed sending request" errors.',
showInDialog: true,
showInDialog: false,
},
maxAttempts: {
type: 'number',
@@ -540,16 +528,6 @@ const SETTINGS_SCHEMA = {
description: 'Hide helpful tips in the UI',
showInDialog: true,
},
escapePastedAtSymbols: {
type: 'boolean',
label: 'Escape Pasted @ Symbols',
category: 'UI',
requiresRestart: false,
default: false,
description:
'When enabled, @ symbols in pasted text are escaped to prevent unintended @path expansion.',
showInDialog: true,
},
showShortcutsHint: {
type: 'boolean',
label: 'Show Shortcuts Hint',
@@ -610,11 +588,11 @@ const SETTINGS_SCHEMA = {
},
hideCWD: {
type: 'boolean',
label: 'Hide CWD',
label: 'Hide Workspace Path',
category: 'UI',
requiresRestart: false,
default: false,
description: 'Hide the current working directory in the footer.',
description: 'Hide the workspace path in the footer.',
showInDialog: true,
},
hideSandboxStatus: {
@@ -1039,20 +1017,6 @@ const SETTINGS_SCHEMA = {
'Apply specific configuration overrides based on matches, with a primary key of model (or alias). The most specific match will be used.',
showInDialog: false,
},
modelDefinitions: {
type: 'object',
label: 'Model Definitions',
category: 'Model',
requiresRestart: true,
default: DEFAULT_MODEL_CONFIGS.modelDefinitions,
description:
'Registry of model metadata, including tier, family, and features.',
showInDialog: false,
additionalProperties: {
type: 'object',
ref: 'ModelDefinition',
},
},
},
},
@@ -1131,16 +1095,6 @@ const SETTINGS_SCHEMA = {
description: 'Model override for the visual agent.',
showInDialog: false,
},
disableUserInput: {
type: 'boolean',
label: 'Disable User Input',
category: 'Advanced',
requiresRestart: false,
default: true,
description:
'Disable user input on browser window during automation.',
showInDialog: false,
},
},
},
},
@@ -1298,8 +1252,8 @@ const SETTINGS_SCHEMA = {
label: 'Sandbox',
category: 'Tools',
requiresRestart: true,
default: undefined as boolean | string | SandboxConfig | undefined,
ref: 'BooleanOrStringOrObject',
default: undefined as boolean | string | undefined,
ref: 'BooleanOrString',
description: oneLine`
Sandbox execution environment.
Set to a boolean to enable or disable the sandbox, provide a string path to a sandbox profile,
@@ -1924,16 +1878,6 @@ const SETTINGS_SCHEMA = {
'Enable web fetch behavior that bypasses LLM summarization.',
showInDialog: true,
},
dynamicModelConfiguration: {
type: 'boolean',
label: 'Dynamic Model Configuration',
category: 'Experimental',
requiresRestart: true,
default: false,
description:
'Enable dynamic model configuration (definitions, resolutions, and chains) via settings.',
showInDialog: false,
},
gemmaModelRouter: {
type: 'object',
label: 'Gemma Model Router',
@@ -2663,44 +2607,9 @@ export const SETTINGS_SCHEMA_DEFINITIONS: Record<
description: 'Accepts either a single string or an array of strings.',
anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],
},
BooleanOrStringOrObject: {
description:
'Accepts either a boolean flag, a string command name, or a configuration object.',
anyOf: [
{ type: 'boolean' },
{ type: 'string' },
{
type: 'object',
description: 'Sandbox configuration object.',
additionalProperties: false,
properties: {
enabled: {
type: 'boolean',
description: 'Enables or disables the sandbox.',
},
command: {
type: 'string',
description:
'The sandbox command to use (docker, podman, sandbox-exec, runsc, lxc).',
enum: ['docker', 'podman', 'sandbox-exec', 'runsc', 'lxc'],
},
image: {
type: 'string',
description: 'The sandbox image to use.',
},
allowedPaths: {
type: 'array',
description:
'A list of absolute host paths that should be accessible within the sandbox.',
items: { type: 'string' },
},
networkAccess: {
type: 'boolean',
description: 'Whether the sandbox should have internet access.',
},
},
},
],
BooleanOrString: {
description: 'Accepts either a boolean flag or a string command name.',
anyOf: [{ type: 'boolean' }, { type: 'string' }],
},
HookDefinitionArray: {
type: 'array',
@@ -2750,25 +2659,6 @@ export const SETTINGS_SCHEMA_DEFINITIONS: Record<
},
},
},
ModelDefinition: {
type: 'object',
description: 'Model metadata registry entry.',
properties: {
displayName: { type: 'string' },
tier: { enum: ['pro', 'flash', 'flash-lite', 'custom', 'auto'] },
family: { type: 'string' },
isPreview: { type: 'boolean' },
dialogLocation: { enum: ['main', 'manual'] },
dialogDescription: { type: 'string' },
features: {
type: 'object',
properties: {
thinking: { type: 'boolean' },
multimodalToolUse: { type: 'boolean' },
},
},
},
},
};
export function getSettingsSchema(): SettingsSchemaType {
@@ -2786,9 +2676,7 @@ type InferSettings<T extends SettingsSchema> = {
? boolean
: T[K]['default'] extends string
? string
: T[K]['default'] extends ReadonlyArray<infer U>
? U[]
: T[K]['default'];
: T[K]['default'];
};
type InferMergedSettings<T extends SettingsSchema> = {
@@ -2802,9 +2690,7 @@ type InferMergedSettings<T extends SettingsSchema> = {
? boolean
: T[K]['default'] extends string
? string
: T[K]['default'] extends ReadonlyArray<infer U>
? U[]
: T[K]['default'];
: T[K]['default'];
};
export type Settings = InferSettings<SettingsSchemaType>;
@@ -19,8 +19,9 @@ import {
isWorkspaceTrusted,
resetTrustedFoldersForTesting,
} from './trustedFolders.js';
import { loadEnvironment, type Settings } from './settings.js';
import { loadEnvironment } from './settings.js';
import { createMockSettings } from '../test-utils/settings.js';
import type { Settings } from './settings.js';
// We explicitly do NOT mock 'fs' or 'proper-lockfile' here to ensure
// we are testing the actual behavior on the real file system.
+2 -8
View File
@@ -4,14 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {
describe,
it,
expect,
vi,
beforeEach,
type MockInstance,
} from 'vitest';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import {
runDeferredCommand,
defer,
@@ -21,6 +14,7 @@ import {
import { ExitCodes } from '@google/gemini-cli-core';
import type { ArgumentsCamelCase, CommandModule } from 'yargs';
import { createMockSettings } from './test-utils/settings.js';
import type { MockInstance } from 'vitest';
const { mockRunExitCleanup, mockCoreEvents } = vi.hoisted(() => ({
mockRunExitCleanup: vi.fn(),
+16 -76
View File
@@ -21,19 +21,15 @@ import {
startInteractiveUI,
getNodeMemoryArgs,
} from './gemini.js';
import {
loadCliConfig,
parseArguments,
type CliArgs,
} from './config/config.js';
import { loadCliConfig, parseArguments } from './config/config.js';
import { loadSandboxConfig } from './config/sandboxConfig.js';
import { createMockSandboxConfig } from '@google/gemini-cli-test-utils';
import { terminalCapabilityManager } from './ui/utils/terminalCapabilityManager.js';
import { start_sandbox } from './utils/sandbox.js';
import { validateNonInteractiveAuth } from './validateNonInterActiveAuth.js';
import os from 'node:os';
import v8 from 'node:v8';
import { loadSettings, type LoadedSettings } from './config/settings.js';
import { type CliArgs } from './config/config.js';
import { type LoadedSettings, loadSettings } from './config/settings.js';
import {
createMockConfig,
createMockSettings,
@@ -193,19 +189,12 @@ vi.mock('./ui/utils/terminalCapabilityManager.js', () => ({
vi.mock('./config/config.js', () => ({
loadCliConfig: vi.fn().mockImplementation(async () => createMockConfig()),
parseArguments: vi.fn().mockResolvedValue({
enabled: true,
allowedPaths: [],
networkAccess: false,
}),
parseArguments: vi.fn().mockResolvedValue({}),
isDebugMode: vi.fn(() => false),
}));
vi.mock('read-package-up', () => ({
readPackageUp: vi.fn().mockResolvedValue({
enabled: true,
allowedPaths: [],
networkAccess: false,
packageJson: { name: 'test-pkg', version: 'test-version' },
path: '/fake/path/package.json',
}),
@@ -243,9 +232,6 @@ vi.mock('./utils/relaunch.js', () => ({
vi.mock('./config/sandboxConfig.js', () => ({
loadSandboxConfig: vi.fn().mockResolvedValue({
enabled: true,
allowedPaths: [],
networkAccess: false,
command: 'docker',
image: 'test-image',
}),
@@ -495,7 +481,6 @@ describe('gemini.tsx main function kitty protocol', () => {
yolo: undefined,
approvalMode: undefined,
policy: undefined,
adminPolicy: undefined,
allowedMcpServerNames: undefined,
allowedTools: undefined,
experimentalAcp: undefined,
@@ -551,9 +536,6 @@ describe('gemini.tsx main function kitty protocol', () => {
);
vi.mocked(parseArguments).mockResolvedValue({
enabled: true,
allowedPaths: [],
networkAccess: false,
promptInteractive: false,
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
@@ -617,9 +599,6 @@ describe('gemini.tsx main function kitty protocol', () => {
});
vi.mocked(parseArguments).mockResolvedValue({
enabled: true,
allowedPaths: [],
networkAccess: false,
promptInteractive: false,
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
@@ -639,17 +618,14 @@ describe('gemini.tsx main function kitty protocol', () => {
const mockConfig = createMockConfig({
isInteractive: () => false,
getQuestion: () => '',
getSandbox: () =>
createMockSandboxConfig({ command: 'docker', image: 'test-image' }),
getSandbox: () => ({ command: 'docker', image: 'test-image' }),
});
vi.mocked(loadCliConfig).mockResolvedValue(mockConfig);
vi.mocked(loadSandboxConfig).mockResolvedValue(
createMockSandboxConfig({
command: 'docker',
image: 'test-image',
}),
);
vi.mocked(loadSandboxConfig).mockResolvedValue({
command: 'docker',
image: 'test-image',
});
process.env['GEMINI_API_KEY'] = 'test-key';
try {
@@ -690,9 +666,6 @@ describe('gemini.tsx main function kitty protocol', () => {
);
vi.mocked(parseArguments).mockResolvedValue({
enabled: true,
allowedPaths: [],
networkAccess: false,
promptInteractive: false,
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
vi.mocked(loadCliConfig).mockResolvedValue(
@@ -748,9 +721,6 @@ describe('gemini.tsx main function kitty protocol', () => {
);
vi.mocked(parseArguments).mockResolvedValue({
enabled: true,
allowedPaths: [],
networkAccess: false,
promptInteractive: false,
resume: 'session-id',
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
@@ -807,9 +777,6 @@ describe('gemini.tsx main function kitty protocol', () => {
);
vi.mocked(parseArguments).mockResolvedValue({
enabled: true,
allowedPaths: [],
networkAccess: false,
promptInteractive: false,
resume: 'latest',
} as unknown as CliArgs);
@@ -860,9 +827,6 @@ describe('gemini.tsx main function kitty protocol', () => {
);
vi.mocked(parseArguments).mockResolvedValue({
enabled: true,
allowedPaths: [],
networkAccess: false,
promptInteractive: false,
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
vi.mocked(loadCliConfig).mockResolvedValue(
@@ -913,9 +877,6 @@ describe('gemini.tsx main function kitty protocol', () => {
);
vi.mocked(parseArguments).mockResolvedValue({
enabled: true,
allowedPaths: [],
networkAccess: false,
promptInteractive: false,
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
vi.mocked(loadCliConfig).mockResolvedValue(
@@ -990,9 +951,6 @@ describe('gemini.tsx main function exit codes', () => {
}),
);
vi.mocked(parseArguments).mockResolvedValue({
enabled: true,
allowedPaths: [],
networkAccess: false,
promptInteractive: true,
} as unknown as CliArgs);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -1009,12 +967,10 @@ describe('gemini.tsx main function exit codes', () => {
it('should exit with 41 for auth failure during sandbox setup', async () => {
vi.stubEnv('SANDBOX', '');
vi.mocked(loadSandboxConfig).mockResolvedValue(
createMockSandboxConfig({
command: 'docker',
image: 'test-image',
}),
);
vi.mocked(loadSandboxConfig).mockResolvedValue({
command: 'docker',
image: 'test-image',
});
vi.mocked(loadCliConfig).mockResolvedValue(
createMockConfig({
refreshAuth: vi.fn().mockRejectedValue(new Error('Auth failed')),
@@ -1054,9 +1010,6 @@ describe('gemini.tsx main function exit codes', () => {
}),
);
vi.mocked(parseArguments).mockResolvedValue({
enabled: true,
allowedPaths: [],
networkAccess: false,
resume: 'invalid-session',
} as unknown as CliArgs);
@@ -1098,11 +1051,7 @@ describe('gemini.tsx main function exit codes', () => {
merged: { security: { auth: {} }, ui: {} },
}),
);
vi.mocked(parseArguments).mockResolvedValue({
enabled: true,
allowedPaths: [],
networkAccess: false,
} as unknown as CliArgs);
vi.mocked(parseArguments).mockResolvedValue({} as unknown as CliArgs);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(process.stdin as any).isTTY = true;
@@ -1137,11 +1086,7 @@ describe('gemini.tsx main function exit codes', () => {
merged: { security: { auth: { selectedType: undefined } }, ui: {} },
}),
);
vi.mocked(parseArguments).mockResolvedValue({
enabled: true,
allowedPaths: [],
networkAccess: false,
} as unknown as CliArgs);
vi.mocked(parseArguments).mockResolvedValue({} as unknown as CliArgs);
runNonInteractiveSpy.mockImplementation(() => Promise.resolve());
@@ -1211,12 +1156,7 @@ describe('project hooks loading based on trust', () => {
const configModule = await import('./config/config.js');
loadCliConfig = vi.mocked(configModule.loadCliConfig);
parseArguments = vi.mocked(configModule.parseArguments);
parseArguments.mockResolvedValue({
enabled: true,
allowedPaths: [],
networkAccess: false,
startupMessages: [],
});
parseArguments.mockResolvedValue({ startupMessages: [] });
const settingsModule = await import('./config/settings.js');
loadSettings = vi.mocked(settingsModule.loadSettings);
+208 -46
View File
@@ -4,53 +4,24 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {
type StartupWarning,
WarningPriority,
type Config,
type ResumedSessionData,
type OutputPayload,
type ConsoleLogPayload,
type UserFeedbackPayload,
sessionId,
logUserPrompt,
AuthType,
UserPromptEvent,
coreEvents,
CoreEvent,
getOauthClient,
patchStdio,
writeToStdout,
writeToStderr,
shouldEnterAlternateScreen,
startupProfiler,
ExitCodes,
SessionStartSource,
SessionEndReason,
ValidationCancelledError,
ValidationRequiredError,
type AdminControlsSettings,
debugLogger,
} from '@google/gemini-cli-core';
import React from 'react';
import { render } from 'ink';
import { AppContainer } from './ui/AppContainer.js';
import { loadCliConfig, parseArguments } from './config/config.js';
import * as cliConfig from './config/config.js';
import { readStdin } from './utils/readStdin.js';
import { basename } from 'node:path';
import { createHash } from 'node:crypto';
import v8 from 'node:v8';
import os from 'node:os';
import dns from 'node:dns';
import { start_sandbox } from './utils/sandbox.js';
import {
loadSettings,
SettingScope,
type DnsResolutionOrder,
type LoadedSettings,
} from './config/settings.js';
import type { DnsResolutionOrder, LoadedSettings } from './config/settings.js';
import {
loadTrustedFolders,
type TrustedFoldersError,
} from './config/trustedFolders.js';
import { loadSettings, SettingScope } from './config/settings.js';
import { getStartupWarnings } from './utils/startupWarnings.js';
import { getUserStartupWarnings } from './utils/userStartupWarnings.js';
import { ConsolePatcher } from './ui/utils/ConsolePatcher.js';
@@ -62,11 +33,47 @@ import {
runExitCleanup,
registerTelemetryConfig,
setupSignalHandlers,
setupTtyCheck,
} from './utils/cleanup.js';
import {
cleanupToolOutputFiles,
cleanupExpiredSessions,
} from './utils/sessionCleanup.js';
import {
type StartupWarning,
WarningPriority,
type Config,
type ResumedSessionData,
type OutputPayload,
type ConsoleLogPayload,
type UserFeedbackPayload,
sessionId,
logUserPrompt,
AuthType,
getOauthClient,
UserPromptEvent,
debugLogger,
recordSlowRender,
coreEvents,
CoreEvent,
createWorkingStdio,
patchStdio,
writeToStdout,
writeToStderr,
disableMouseEvents,
enableMouseEvents,
disableLineWrapping,
enableLineWrapping,
shouldEnterAlternateScreen,
startupProfiler,
ExitCodes,
SessionStartSource,
SessionEndReason,
getVersion,
ValidationCancelledError,
ValidationRequiredError,
type AdminControlsSettings,
} from '@google/gemini-cli-core';
import {
initializeApp,
type InitializationResult,
@@ -74,9 +81,19 @@ import {
import { validateAuthMethod } from './config/auth.js';
import { runAcpClient } from './acp/acpClient.js';
import { validateNonInteractiveAuth } from './validateNonInterActiveAuth.js';
import { checkForUpdates } from './ui/utils/updateCheck.js';
import { handleAutoUpdate } from './utils/handleAutoUpdate.js';
import { appEvents, AppEvent } from './utils/events.js';
import { SessionError, SessionSelector } from './utils/sessionUtils.js';
import { SettingsContext } from './ui/contexts/SettingsContext.js';
import { MouseProvider } from './ui/contexts/MouseContext.js';
import { StreamingState } from './ui/types.js';
import { computeTerminalTitle } from './utils/windowTitle.js';
import { SessionStatsProvider } from './ui/contexts/SessionContext.js';
import { VimModeProvider } from './ui/contexts/VimModeContext.js';
import { KeypressProvider } from './ui/contexts/KeypressContext.js';
import { useKittyKeyboardProtocol } from './ui/hooks/useKittyKeyboardProtocol.js';
import {
relaunchAppInChildProcess,
relaunchOnExitCode,
@@ -84,13 +101,18 @@ import {
import { loadSandboxConfig } from './config/sandboxConfig.js';
import { deleteSession, listSessions } from './utils/sessions.js';
import { createPolicyUpdater } from './config/policy.js';
import { ScrollProvider } from './ui/contexts/ScrollProvider.js';
import { TerminalProvider } from './ui/contexts/TerminalContext.js';
import { isAlternateBufferEnabled } from './ui/hooks/useAlternateBuffer.js';
import { OverflowProvider } from './ui/contexts/OverflowContext.js';
import { setupTerminalAndTheme } from './utils/terminalTheme.js';
import { profiler } from './ui/components/DebugProfiler.js';
import { runDeferredCommand } from './deferred.js';
import { cleanupBackgroundLogs } from './utils/logCleanup.js';
import { SlashCommandConflictHandler } from './services/SlashCommandConflictHandler.js';
const SLOW_RENDER_MS = 200;
export function validateDnsResolutionOrder(
order: string | undefined,
): DnsResolutionOrder {
@@ -169,16 +191,138 @@ export async function startInteractiveUI(
resumedSessionData: ResumedSessionData | undefined,
initializationResult: InitializationResult,
) {
// Dynamically import the heavy UI module so React/Ink are only parsed when needed
const { startInteractiveUI: doStartUI } = await import('./interactiveCli.js');
await doStartUI(
config,
settings,
startupWarnings,
workspaceRoot,
resumedSessionData,
initializationResult,
// Never enter Ink alternate buffer mode when screen reader mode is enabled
// as there is no benefit of alternate buffer mode when using a screen reader
// and the Ink alternate buffer mode requires line wrapping harmful to
// screen readers.
const useAlternateBuffer = shouldEnterAlternateScreen(
isAlternateBufferEnabled(config),
config.getScreenReader(),
);
const mouseEventsEnabled = useAlternateBuffer;
if (mouseEventsEnabled) {
enableMouseEvents();
registerCleanup(() => {
disableMouseEvents();
});
}
const version = await getVersion();
setWindowTitle(basename(workspaceRoot), settings);
const consolePatcher = new ConsolePatcher({
onNewMessage: (msg) => {
coreEvents.emitConsoleLog(msg.type, msg.content);
},
debugMode: config.getDebugMode(),
});
consolePatcher.patch();
registerCleanup(consolePatcher.cleanup);
const { stdout: inkStdout, stderr: inkStderr } = createWorkingStdio();
const isShpool = !!process.env['SHPOOL_SESSION_NAME'];
// Create wrapper component to use hooks inside render
const AppWrapper = () => {
useKittyKeyboardProtocol();
return (
<SettingsContext.Provider value={settings}>
<KeypressProvider
config={config}
debugKeystrokeLogging={settings.merged.general.debugKeystrokeLogging}
>
<MouseProvider
mouseEventsEnabled={mouseEventsEnabled}
debugKeystrokeLogging={
settings.merged.general.debugKeystrokeLogging
}
>
<TerminalProvider>
<ScrollProvider>
<OverflowProvider>
<SessionStatsProvider>
<VimModeProvider>
<AppContainer
config={config}
startupWarnings={startupWarnings}
version={version}
resumedSessionData={resumedSessionData}
initializationResult={initializationResult}
/>
</VimModeProvider>
</SessionStatsProvider>
</OverflowProvider>
</ScrollProvider>
</TerminalProvider>
</MouseProvider>
</KeypressProvider>
</SettingsContext.Provider>
);
};
if (isShpool) {
// Wait a moment for shpool to stabilize terminal size and state.
// shpool is a persistence tool that restores terminal state by replaying it.
// This delay gives shpool time to finish its restoration replay and send
// the actual terminal size (often via an immediate SIGWINCH) before we
// render the first TUI frame. Without this, the first frame may be
// garbled or rendered at an incorrect size, which disabling incremental
// rendering alone cannot fix for the initial frame.
await new Promise((resolve) => setTimeout(resolve, 100));
}
const instance = render(
process.env['DEBUG'] ? (
<React.StrictMode>
<AppWrapper />
</React.StrictMode>
) : (
<AppWrapper />
),
{
stdout: inkStdout,
stderr: inkStderr,
stdin: process.stdin,
exitOnCtrlC: false,
isScreenReaderEnabled: config.getScreenReader(),
onRender: ({ renderTime }: { renderTime: number }) => {
if (renderTime > SLOW_RENDER_MS) {
recordSlowRender(config, renderTime);
}
profiler.reportFrameRendered();
},
patchConsole: false,
alternateBuffer: useAlternateBuffer,
incrementalRendering:
settings.merged.ui.incrementalRendering !== false &&
useAlternateBuffer &&
!isShpool,
},
);
if (useAlternateBuffer) {
disableLineWrapping();
registerCleanup(() => {
enableLineWrapping();
});
}
checkForUpdates(settings)
.then((info) => {
handleAutoUpdate(info, settings, config.getProjectRoot());
})
.catch((err) => {
// Silently ignore update check errors.
if (config.getDebugMode()) {
debugLogger.warn('Update check failed:', err);
}
});
registerCleanup(() => instance.unmount());
registerCleanup(setupTtyCheck());
}
export async function main() {
@@ -226,7 +370,6 @@ export async function main() {
await Promise.all([
cleanupCheckpoints(),
cleanupToolOutputFiles(settings.merged),
cleanupBackgroundLogs(),
]);
const parseArgsHandle = startupProfiler.start('parse_arguments');
@@ -685,6 +828,25 @@ export async function main() {
}
}
function setWindowTitle(title: string, settings: LoadedSettings) {
if (!settings.merged.ui.hideWindowTitle) {
// Initial state before React loop starts
const windowTitle = computeTerminalTitle({
streamingState: StreamingState.Idle,
isConfirming: false,
isSilentWorking: false,
folderName: title,
showThoughts: !!settings.merged.ui.showStatusInTitle,
useDynamicTitle: settings.merged.ui.dynamicWindowTitle,
});
writeToStdout(`\x1b]0;${windowTitle}\x07`);
process.on('exit', () => {
writeToStdout(`\x1b]0;\x07`);
});
}
}
export function initializeOutputListenersAndFlush() {
// If there are no listeners for output, make sure we flush so output is not
// lost.
+2 -1
View File
@@ -6,7 +6,8 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { main } from './gemini.js';
import { debugLogger, type Config } from '@google/gemini-cli-core';
import { debugLogger } from '@google/gemini-cli-core';
import { type Config } from '@google/gemini-cli-core';
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const actual =
-214
View File
@@ -1,214 +0,0 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import React from 'react';
import { render } from 'ink';
import { basename } from 'node:path';
import { AppContainer } from './ui/AppContainer.js';
import { ConsolePatcher } from './ui/utils/ConsolePatcher.js';
import { registerCleanup, setupTtyCheck } from './utils/cleanup.js';
import {
type StartupWarning,
type Config,
type ResumedSessionData,
coreEvents,
createWorkingStdio,
disableMouseEvents,
enableMouseEvents,
disableLineWrapping,
enableLineWrapping,
shouldEnterAlternateScreen,
recordSlowRender,
writeToStdout,
getVersion,
debugLogger,
} from '@google/gemini-cli-core';
import type { InitializationResult } from './core/initializer.js';
import type { LoadedSettings } from './config/settings.js';
import { checkForUpdates } from './ui/utils/updateCheck.js';
import { handleAutoUpdate } from './utils/handleAutoUpdate.js';
import { SettingsContext } from './ui/contexts/SettingsContext.js';
import { MouseProvider } from './ui/contexts/MouseContext.js';
import { StreamingState } from './ui/types.js';
import { computeTerminalTitle } from './utils/windowTitle.js';
import { SessionStatsProvider } from './ui/contexts/SessionContext.js';
import { VimModeProvider } from './ui/contexts/VimModeContext.js';
import { KeyMatchersProvider } from './ui/hooks/useKeyMatchers.js';
import { loadKeyMatchers } from './ui/key/keyMatchers.js';
import { KeypressProvider } from './ui/contexts/KeypressContext.js';
import { useKittyKeyboardProtocol } from './ui/hooks/useKittyKeyboardProtocol.js';
import { ScrollProvider } from './ui/contexts/ScrollProvider.js';
import { TerminalProvider } from './ui/contexts/TerminalContext.js';
import { isAlternateBufferEnabled } from './ui/hooks/useAlternateBuffer.js';
import { OverflowProvider } from './ui/contexts/OverflowContext.js';
import { profiler } from './ui/components/DebugProfiler.js';
const SLOW_RENDER_MS = 200;
export async function startInteractiveUI(
config: Config,
settings: LoadedSettings,
startupWarnings: StartupWarning[],
workspaceRoot: string = process.cwd(),
resumedSessionData: ResumedSessionData | undefined,
initializationResult: InitializationResult,
) {
// Never enter Ink alternate buffer mode when screen reader mode is enabled
// as there is no benefit of alternate buffer mode when using a screen reader
// and the Ink alternate buffer mode requires line wrapping harmful to
// screen readers.
const useAlternateBuffer = shouldEnterAlternateScreen(
isAlternateBufferEnabled(config),
config.getScreenReader(),
);
const mouseEventsEnabled = useAlternateBuffer;
if (mouseEventsEnabled) {
enableMouseEvents();
registerCleanup(() => {
disableMouseEvents();
});
}
const { matchers, errors } = await loadKeyMatchers();
errors.forEach((error) => {
coreEvents.emitFeedback('warning', error);
});
const version = await getVersion();
setWindowTitle(basename(workspaceRoot), settings);
const consolePatcher = new ConsolePatcher({
onNewMessage: (msg) => {
coreEvents.emitConsoleLog(msg.type, msg.content);
},
debugMode: config.getDebugMode(),
});
consolePatcher.patch();
registerCleanup(consolePatcher.cleanup);
const { stdout: inkStdout, stderr: inkStderr } = createWorkingStdio();
const isShpool = !!process.env['SHPOOL_SESSION_NAME'];
// Create wrapper component to use hooks inside render
const AppWrapper = () => {
useKittyKeyboardProtocol();
return (
<SettingsContext.Provider value={settings}>
<KeyMatchersProvider value={matchers}>
<KeypressProvider
config={config}
debugKeystrokeLogging={
settings.merged.general.debugKeystrokeLogging
}
>
<MouseProvider
mouseEventsEnabled={mouseEventsEnabled}
debugKeystrokeLogging={
settings.merged.general.debugKeystrokeLogging
}
>
<TerminalProvider>
<ScrollProvider>
<OverflowProvider>
<SessionStatsProvider>
<VimModeProvider>
<AppContainer
config={config}
startupWarnings={startupWarnings}
version={version}
resumedSessionData={resumedSessionData}
initializationResult={initializationResult}
/>
</VimModeProvider>
</SessionStatsProvider>
</OverflowProvider>
</ScrollProvider>
</TerminalProvider>
</MouseProvider>
</KeypressProvider>
</KeyMatchersProvider>
</SettingsContext.Provider>
);
};
if (isShpool) {
// Wait a moment for shpool to stabilize terminal size and state.
await new Promise((resolve) => setTimeout(resolve, 100));
}
const instance = render(
process.env['DEBUG'] ? (
<React.StrictMode>
<AppWrapper />
</React.StrictMode>
) : (
<AppWrapper />
),
{
stdout: inkStdout,
stderr: inkStderr,
stdin: process.stdin,
exitOnCtrlC: false,
isScreenReaderEnabled: config.getScreenReader(),
onRender: ({ renderTime }: { renderTime: number }) => {
if (renderTime > SLOW_RENDER_MS) {
recordSlowRender(config, renderTime);
}
profiler.reportFrameRendered();
},
patchConsole: false,
alternateBuffer: useAlternateBuffer,
incrementalRendering:
settings.merged.ui.incrementalRendering !== false &&
useAlternateBuffer &&
!isShpool,
},
);
if (useAlternateBuffer) {
disableLineWrapping();
registerCleanup(() => {
enableLineWrapping();
});
}
checkForUpdates(settings)
.then((info) => {
handleAutoUpdate(info, settings, config.getProjectRoot());
})
.catch((err) => {
// Silently ignore update check errors.
if (config.getDebugMode()) {
debugLogger.warn('Update check failed:', err);
}
});
registerCleanup(() => instance.unmount());
registerCleanup(setupTtyCheck());
}
function setWindowTitle(title: string, settings: LoadedSettings) {
if (!settings.merged.ui.hideWindowTitle) {
// Initial state before React loop starts
const windowTitle = computeTerminalTitle({
streamingState: StreamingState.Idle,
isConfirming: false,
isSilentWorking: false,
folderName: title,
showThoughts: !!settings.merged.ui.showStatusInTitle,
useDynamicTitle: settings.merged.ui.dynamicWindowTitle,
});
writeToStdout(`\x1b]0;${windowTitle}\x07`);
process.on('exit', () => {
writeToStdout(`\x1b]0;\x07`);
});
}
}
+2 -2
View File
@@ -211,7 +211,7 @@ export async function runNonInteractive({
const geminiClient = config.getGeminiClient();
const scheduler = new Scheduler({
context: config,
config,
messageBus: config.getMessageBus(),
getPreferredEditor: () => undefined,
schedulerId: ROOT_SCHEDULER_ID,
@@ -263,8 +263,8 @@ export async function runNonInteractive({
onDebugMessage: () => {},
messageId: Date.now(),
signal: abortController.signal,
escapePastedAtSymbols: false,
});
if (error || !processedQuery) {
// An error occurred during @include processing (e.g., file not found).
// The error message is already logged by handleAtCommand.
@@ -52,7 +52,8 @@ vi.mock('../ui/commands/permissionsCommand.js', async () => {
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
import { BuiltinCommandLoader } from './BuiltinCommandLoader.js';
import { isNightly, type Config } from '@google/gemini-cli-core';
import type { Config } from '@google/gemini-cli-core';
import { isNightly } from '@google/gemini-cli-core';
import { CommandKind } from '../ui/commands/types.js';
import { restoreCommand } from '../ui/commands/restoreCommand.js';
@@ -29,7 +29,7 @@ import { compressCommand } from '../ui/commands/compressCommand.js';
import { copyCommand } from '../ui/commands/copyCommand.js';
import { corgiCommand } from '../ui/commands/corgiCommand.js';
import { docsCommand } from '../ui/commands/docsCommand.js';
import { directoryCommand } from '../ui/commands/directoryCommand.js';
import { workspaceCommand } from '../ui/commands/workspaceCommand.js';
import { editorCommand } from '../ui/commands/editorCommand.js';
import { extensionsCommand } from '../ui/commands/extensionsCommand.js';
import { footerCommand } from '../ui/commands/footerCommand.js';
@@ -131,7 +131,7 @@ export class BuiltinCommandLoader implements ICommandLoader {
copyCommand,
corgiCommand,
docsCommand,
directoryCommand,
workspaceCommand,
editorCommand,
...(this.config?.getExtensionsEnabled() === false
? [
@@ -6,7 +6,8 @@
import * as glob from 'glob';
import * as path from 'node:path';
import { GEMINI_DIR, Storage, type Config } from '@google/gemini-cli-core';
import type { Config } from '@google/gemini-cli-core';
import { GEMINI_DIR, Storage } from '@google/gemini-cli-core';
import mock from 'mock-fs';
import { FileCommandLoader } from './FileCommandLoader.js';
import { assert, vi } from 'vitest';
@@ -9,7 +9,8 @@ import path from 'node:path';
import toml from '@iarna/toml';
import { glob } from 'glob';
import { z } from 'zod';
import { Storage, coreEvents, type Config } from '@google/gemini-cli-core';
import type { Config } from '@google/gemini-cli-core';
import { Storage, coreEvents } from '@google/gemini-cli-core';
import type { ICommandLoader } from './types.js';
import type {
CommandContext,
+7 -10
View File
@@ -4,17 +4,14 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {
getErrorMessage,
getMCPServerPrompts,
type Config,
} from '@google/gemini-cli-core';
import {
CommandKind,
type CommandContext,
type SlashCommand,
type SlashCommandActionReturn,
import type { Config } from '@google/gemini-cli-core';
import { getErrorMessage, getMCPServerPrompts } from '@google/gemini-cli-core';
import type {
CommandContext,
SlashCommand,
SlashCommandActionReturn,
} from '../ui/commands/types.js';
import { CommandKind } from '../ui/commands/types.js';
import type { ICommandLoader } from './types.js';
import type { PromptArgument } from '@modelcontextprotocol/sdk/types.js';
@@ -4,7 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { CommandKind, type SlashCommand } from '../ui/commands/types.js';
import type { SlashCommand } from '../ui/commands/types.js';
import { CommandKind } from '../ui/commands/types.js';
import type { CommandConflict } from './types.js';
/**
@@ -6,7 +6,8 @@
import { vi } from 'vitest';
import type { CommandContext } from '../ui/commands/types.js';
import { mergeSettings, type LoadedSettings } from '../config/settings.js';
import type { LoadedSettings } from '../config/settings.js';
import { mergeSettings } from '../config/settings.js';
import type { GitService } from '@google/gemini-cli-core';
import type { SessionStatsState } from '../ui/contexts/SessionContext.js';
+3 -6
View File
@@ -6,11 +6,8 @@
import { vi } from 'vitest';
import type { Config } from '@google/gemini-cli-core';
import {
createTestMergedSettings,
type LoadedSettings,
type Settings,
} from '../config/settings.js';
import type { LoadedSettings, Settings } from '../config/settings.js';
import { createTestMergedSettings } from '../config/settings.js';
/**
* Creates a mocked Config object with default values and allows overrides.
@@ -128,7 +125,7 @@ export const createMockConfig = (overrides: Partial<Config> = {}): Config =>
getEnableInteractiveShell: vi.fn().mockReturnValue(false),
getSkipNextSpeakerCheck: vi.fn().mockReturnValue(false),
getContinueOnFailedApiCall: vi.fn().mockReturnValue(false),
getRetryFetchErrors: vi.fn().mockReturnValue(true),
getRetryFetchErrors: vi.fn().mockReturnValue(false),
getEnableShellOutputEfficiency: vi.fn().mockReturnValue(true),
getShellToolInactivityTimeout: vi.fn().mockReturnValue(300000),
getShellExecutionConfig: vi.fn().mockReturnValue({}),
+1 -1
View File
@@ -11,10 +11,10 @@ import {
} from 'ink';
import { EventEmitter } from 'node:events';
import { Box } from 'ink';
import type React from 'react';
import { Terminal } from '@xterm/headless';
import { vi } from 'vitest';
import stripAnsi from 'strip-ansi';
import type React from 'react';
import { act, useState } from 'react';
import os from 'node:os';
import path from 'node:path';
+1 -56
View File
@@ -2770,7 +2770,7 @@ describe('AppContainer State Management', () => {
unmount();
});
it('should exit copy mode on non-scroll key press', async () => {
it('should exit copy mode on any key press', async () => {
await setupCopyModeTest(isAlternateMode);
// Enter copy mode
@@ -2792,61 +2792,6 @@ describe('AppContainer State Management', () => {
unmount();
});
it('should not exit copy mode on PageDown and should pass it through', async () => {
const childHandler = vi.fn().mockReturnValue(false);
await setupCopyModeTest(true, childHandler);
// Enter copy mode
act(() => {
stdin.write('\x13'); // Ctrl+S
});
rerender();
expect(disableMouseEvents).toHaveBeenCalled();
childHandler.mockClear();
(enableMouseEvents as Mock).mockClear();
// PageDown should be passed through to lower-priority handlers.
act(() => {
stdin.write('\x1b[6~');
});
rerender();
expect(enableMouseEvents).not.toHaveBeenCalled();
expect(childHandler).toHaveBeenCalled();
expect(childHandler).toHaveBeenCalledWith(
expect.objectContaining({ name: 'pagedown' }),
);
unmount();
});
it('should not exit copy mode on Shift+Down and should pass it through', async () => {
const childHandler = vi.fn().mockReturnValue(false);
await setupCopyModeTest(true, childHandler);
// Enter copy mode
act(() => {
stdin.write('\x13'); // Ctrl+S
});
rerender();
expect(disableMouseEvents).toHaveBeenCalled();
childHandler.mockClear();
(enableMouseEvents as Mock).mockClear();
act(() => {
stdin.write('\x1b[1;2B'); // Shift+Down
});
rerender();
expect(enableMouseEvents).not.toHaveBeenCalled();
expect(childHandler).toHaveBeenCalled();
expect(childHandler).toHaveBeenCalledWith(
expect.objectContaining({ name: 'down', shift: true }),
);
unmount();
});
it('should have higher priority than other priority listeners when enabled', async () => {
// 1. Initial state with a child component's priority listener (already subscribed)
// It should NOT handle Ctrl+S so we can enter copy mode.
+4 -32
View File
@@ -162,7 +162,6 @@ import {
import { LoginWithGoogleRestartDialog } from './auth/LoginWithGoogleRestartDialog.js';
import { NewAgentsChoice } from './components/NewAgentsNotification.js';
import { isSlashCommand } from './utils/commandUtils.js';
import { parseSlashCommand } from '../utils/commands.js';
import { useTerminalTheme } from './hooks/useTerminalTheme.js';
import { useTimedMessage } from './hooks/useTimedMessage.js';
import { useIsHelpDismissKey } from './utils/shortcutsHelp.js';
@@ -474,11 +473,9 @@ export const AppContainer = (props: AppContainerProps) => {
disableMouseEvents();
// Kill all background shells
await Promise.all(
Array.from(backgroundShellsRef.current.keys()).map((pid) =>
ShellExecutionService.kill(pid),
),
);
for (const pid of backgroundShellsRef.current.keys()) {
ShellExecutionService.kill(pid);
}
const ideClient = await IdeClient.getInstance();
await ideClient.disconnect();
@@ -1290,18 +1287,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
...pendingGeminiHistoryItems,
]);
if (isSlash && isAgentRunning) {
const { commandToExecute } = parseSlashCommand(
submittedValue,
slashCommands ?? [],
);
if (commandToExecute?.isSafeConcurrent) {
void handleSlashCommand(submittedValue);
addInput(submittedValue);
return;
}
}
if (config.isModelSteeringEnabled() && isAgentRunning && !isSlash) {
handleHintSubmit(submittedValue);
addInput(submittedValue);
@@ -1345,8 +1330,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
addMessage,
addInput,
submitQuery,
handleSlashCommand,
slashCommands,
isMcpReady,
streamingState,
messageQueue.length,
@@ -1879,18 +1862,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
useKeypress(handleGlobalKeypress, { isActive: true, priority: true });
useKeypress(
(key: Key) => {
if (
keyMatchers[Command.SCROLL_UP](key) ||
keyMatchers[Command.SCROLL_DOWN](key) ||
keyMatchers[Command.PAGE_UP](key) ||
keyMatchers[Command.PAGE_DOWN](key) ||
keyMatchers[Command.SCROLL_HOME](key) ||
keyMatchers[Command.SCROLL_END](key)
) {
return false;
}
() => {
setCopyModeEnabled(false);
enableMouseEvents();
return true;
+2 -4
View File
@@ -6,10 +6,8 @@
import type { IdeInfo } from '@google/gemini-cli-core';
import { Box, Text } from 'ink';
import {
RadioButtonSelect,
type RadioSelectItem,
} from './components/shared/RadioButtonSelect.js';
import type { RadioSelectItem } from './components/shared/RadioButtonSelect.js';
import { RadioButtonSelect } from './components/shared/RadioButtonSelect.js';
import { useKeypress } from './hooks/useKeypress.js';
import { theme } from './semantic-colors.js';
+4 -4
View File
@@ -9,11 +9,11 @@ import { useCallback, useState } from 'react';
import { Box, Text } from 'ink';
import { theme } from '../semantic-colors.js';
import { RadioButtonSelect } from '../components/shared/RadioButtonSelect.js';
import {
SettingScope,
type LoadableSettingScope,
type LoadedSettings,
import type {
LoadableSettingScope,
LoadedSettings,
} from '../../config/settings.js';
import { SettingScope } from '../../config/settings.js';
import {
AuthType,
clearCachedCredentialFile,
+2 -6
View File
@@ -4,11 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {
CommandKind,
type CommandContext,
type SlashCommand,
} from './types.js';
import type { CommandContext, SlashCommand } from './types.js';
import { CommandKind } from './types.js';
import process from 'node:process';
import { MessageType, type HistoryItemAbout } from '../types.js';
import {
@@ -23,7 +20,6 @@ export const aboutCommand: SlashCommand = {
description: 'Show version info',
kind: CommandKind.BUILT_IN,
autoExecute: true,
isSafeConcurrent: true,
action: async (context) => {
const osVersion = process.platform;
let sandboxEnv = 'no sandbox';
@@ -4,7 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { vi, describe, it, expect, beforeEach, type Mock } from 'vitest';
import type { Mock } from 'vitest';
import { vi, describe, it, expect, beforeEach } from 'vitest';
import { clearCommand } from './clearCommand.js';
import { type CommandContext } from './types.js';
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
@@ -16,12 +17,12 @@ vi.mock('@google/gemini-cli-core', async () => {
...actual,
uiTelemetryService: {
setLastPromptTokenCount: vi.fn(),
clear: vi.fn(),
},
};
});
import { uiTelemetryService, type GeminiClient } from '@google/gemini-cli-core';
import type { GeminiClient } from '@google/gemini-cli-core';
import { uiTelemetryService } from '@google/gemini-cli-core';
describe('clearCommand', () => {
let mockContext: CommandContext;
@@ -73,16 +74,17 @@ describe('clearCommand', () => {
expect(mockResetChat).toHaveBeenCalledTimes(1);
expect(mockHintClear).toHaveBeenCalledTimes(1);
expect(uiTelemetryService.clear).toHaveBeenCalled();
expect(uiTelemetryService.clear).toHaveBeenCalledTimes(1);
expect(uiTelemetryService.setLastPromptTokenCount).toHaveBeenCalledWith(0);
expect(uiTelemetryService.setLastPromptTokenCount).toHaveBeenCalledTimes(1);
expect(mockContext.ui.clear).toHaveBeenCalledTimes(1);
// Check the order of operations.
const setDebugMessageOrder = (mockContext.ui.setDebugMessage as Mock).mock
.invocationCallOrder[0];
const resetChatOrder = mockResetChat.mock.invocationCallOrder[0];
const resetTelemetryOrder = (uiTelemetryService.clear as Mock).mock
.invocationCallOrder[0];
const resetTelemetryOrder = (
uiTelemetryService.setLastPromptTokenCount as Mock
).mock.invocationCallOrder[0];
const clearOrder = (mockContext.ui.clear as Mock).mock
.invocationCallOrder[0];
@@ -108,8 +110,8 @@ describe('clearCommand', () => {
'Clearing terminal.',
);
expect(mockResetChat).not.toHaveBeenCalled();
expect(uiTelemetryService.clear).toHaveBeenCalled();
expect(uiTelemetryService.clear).toHaveBeenCalledTimes(1);
expect(uiTelemetryService.setLastPromptTokenCount).toHaveBeenCalledWith(0);
expect(uiTelemetryService.setLastPromptTokenCount).toHaveBeenCalledTimes(1);
expect(nullConfigContext.ui.clear).toHaveBeenCalledTimes(1);
});
});
+17 -14
View File
@@ -10,7 +10,8 @@ import {
SessionStartSource,
flushTelemetry,
} from '@google/gemini-cli-core';
import { CommandKind, type SlashCommand } from './types.js';
import type { SlashCommand } from './types.js';
import { CommandKind } from './types.js';
import { MessageType } from '../types.js';
import { randomUUID } from 'node:crypto';
@@ -22,6 +23,10 @@ export const clearCommand: SlashCommand = {
action: async (context, _args) => {
const geminiClient = context.services.config?.getGeminiClient();
const config = context.services.config;
const chatRecordingService = context.services.config
?.getGeminiClient()
?.getChat()
.getChatRecordingService();
// Fire SessionEnd hook before clearing
const hookSystem = config?.getHookSystem();
@@ -29,18 +34,6 @@ export const clearCommand: SlashCommand = {
await hookSystem.fireSessionEndEvent(SessionEndReason.Clear);
}
// Reset user steering hints
config?.userHintService.clear();
// Start a new conversation recording with a new session ID
// We MUST do this before calling resetChat() so the new ChatRecordingService
// initialized by GeminiChat picks up the new session ID.
let newSessionId: string | undefined;
if (config) {
newSessionId = randomUUID();
config.setSessionId(newSessionId);
}
if (geminiClient) {
context.ui.setDebugMessage('Clearing terminal and resetting chat.');
// If resetChat fails, the exception will propagate and halt the command,
@@ -50,6 +43,16 @@ export const clearCommand: SlashCommand = {
context.ui.setDebugMessage('Clearing terminal.');
}
// Reset user steering hints
config?.userHintService.clear();
// Start a new conversation recording with a new session ID
if (config && chatRecordingService) {
const newSessionId = randomUUID();
config.setSessionId(newSessionId);
chatRecordingService.initialize();
}
// Fire SessionStart hook after clearing
let result;
if (hookSystem) {
@@ -66,7 +69,7 @@ export const clearCommand: SlashCommand = {
await flushTelemetry(config);
}
uiTelemetryService.clear(newSessionId);
uiTelemetryService.setLastPromptTokenCount(0);
context.ui.clear();
if (result?.systemMessage) {
@@ -4,8 +4,10 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { MessageType, type HistoryItemCompression } from '../types.js';
import { CommandKind, type SlashCommand } from './types.js';
import type { HistoryItemCompression } from '../types.js';
import { MessageType } from '../types.js';
import type { SlashCommand } from './types.js';
import { CommandKind } from './types.js';
export const compressCommand: SlashCommand = {
name: 'compress',
@@ -4,7 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { vi, describe, it, expect, beforeEach, type Mock } from 'vitest';
import type { Mock } from 'vitest';
import { vi, describe, it, expect, beforeEach } from 'vitest';
import { copyCommand } from './copyCommand.js';
import { type CommandContext } from './types.js';
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
+2 -5
View File
@@ -6,11 +6,8 @@
import { debugLogger } from '@google/gemini-cli-core';
import { copyToClipboard } from '../utils/commandUtils.js';
import {
CommandKind,
type SlashCommand,
type SlashCommandActionReturn,
} from './types.js';
import type { SlashCommand, SlashCommandActionReturn } from './types.js';
import { CommandKind } from './types.js';
export const copyCommand: SlashCommand = {
name: 'copy',
@@ -6,9 +6,10 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { helpCommand } from './helpCommand.js';
import { CommandKind, type CommandContext } from './types.js';
import { type CommandContext } from './types.js';
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
import { MessageType } from '../types.js';
import { CommandKind } from './types.js';
describe('helpCommand', () => {
let mockContext: CommandContext;
+2 -1
View File
@@ -4,7 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { CommandKind, type SlashCommand } from './types.js';
import type { SlashCommand } from './types.js';
import { CommandKind } from './types.js';
import { MessageType, type HistoryItemHelp } from '../types.js';
export const helpCommand: SlashCommand = {
@@ -7,12 +7,8 @@
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
import { hooksCommand } from './hooksCommand.js';
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
import {
HookType,
HookEventName,
ConfigSource,
type HookRegistryEntry,
} from '@google/gemini-cli-core';
import type { HookRegistryEntry } from '@google/gemini-cli-core';
import { HookType, HookEventName, ConfigSource } from '@google/gemini-cli-core';
import type { CommandContext } from './types.js';
import { SettingScope } from '../../config/settings.js';
@@ -4,15 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {
vi,
describe,
it,
expect,
beforeEach,
afterEach,
type MockInstance,
} from 'vitest';
import type { MockInstance } from 'vitest';
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
import { ideCommand } from './ideCommand.js';
import { type CommandContext } from './types.js';
import { IDE_DEFINITIONS } from '@google/gemini-cli-core';
@@ -4,7 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { vi, describe, it, expect, beforeEach, type Mock } from 'vitest';
import type { Mock } from 'vitest';
import { vi, describe, it, expect, beforeEach } from 'vitest';
import { memoryCommand } from './memoryCommand.js';
import type { SlashCommand, CommandContext } from './types.js';
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
@@ -11,11 +11,8 @@ import {
showMemory,
} from '@google/gemini-cli-core';
import { MessageType } from '../types.js';
import {
CommandKind,
type SlashCommand,
type SlashCommandActionReturn,
} from './types.js';
import type { SlashCommand, SlashCommandActionReturn } from './types.js';
import { CommandKind } from './types.js';
export const memoryCommand: SlashCommand = {
name: 'memory',
@@ -4,11 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {
CommandKind,
type OpenDialogActionReturn,
type SlashCommand,
} from './types.js';
import type { OpenDialogActionReturn, SlashCommand } from './types.js';
import { CommandKind } from './types.js';
export const privacyCommand: SlashCommand = {
name: 'privacy',
@@ -4,18 +4,14 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {
CommandKind,
type OpenDialogActionReturn,
type SlashCommand,
} from './types.js';
import type { OpenDialogActionReturn, SlashCommand } from './types.js';
import { CommandKind } from './types.js';
export const settingsCommand: SlashCommand = {
name: 'settings',
description: 'View and edit Gemini CLI settings',
kind: CommandKind.BUILT_IN,
autoExecute: true,
isSafeConcurrent: true,
action: (_context, _args): OpenDialogActionReturn => ({
type: 'dialog',
dialog: 'settings',
@@ -17,7 +17,8 @@ import {
} from './setupGithubCommand.js';
import type { CommandContext } from './types.js';
import * as commandUtils from '../utils/commandUtils.js';
import { debugLogger, type ToolActionReturn } from '@google/gemini-cli-core';
import type { ToolActionReturn } from '@google/gemini-cli-core';
import { debugLogger } from '@google/gemini-cli-core';
vi.mock('child_process');
@@ -17,11 +17,8 @@ import {
getGitHubRepoInfo,
} from '../../utils/gitUtils.js';
import {
CommandKind,
type SlashCommand,
type SlashCommandActionReturn,
} from './types.js';
import type { SlashCommand, SlashCommandActionReturn } from './types.js';
import { CommandKind } from './types.js';
import { getUrlOpenCommand } from '../../ui/utils/commandUtils.js';
import { debugLogger } from '@google/gemini-cli-core';
@@ -123,6 +120,7 @@ async function downloadFiles({
downloads.push(
(async () => {
const endpoint = `${REPO_DOWNLOAD_URL}/refs/tags/${releaseTag}/${SOURCE_DIR}/${fileBasename}`;
// eslint-disable-next-line no-restricted-syntax -- TODO: Migrate to safeFetch for SSRF protection
const response = await fetch(endpoint, {
method: 'GET',
dispatcher: proxy ? new ProxyAgent(proxy) : undefined,
@@ -4,7 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { CommandKind, type SlashCommand } from './types.js';
import type { SlashCommand } from './types.js';
import { CommandKind } from './types.js';
export const shortcutsCommand: SlashCommand = {
name: 'shortcuts',
@@ -84,7 +84,6 @@ export const statsCommand: SlashCommand = {
description: 'Check session stats. Usage: /stats [session|model|tools]',
kind: CommandKind.BUILT_IN,
autoExecute: false,
isSafeConcurrent: true,
action: async (context: CommandContext) => {
await defaultSessionView(context);
},
@@ -94,7 +93,6 @@ export const statsCommand: SlashCommand = {
description: 'Show session-specific usage statistics',
kind: CommandKind.BUILT_IN,
autoExecute: true,
isSafeConcurrent: true,
action: async (context: CommandContext) => {
await defaultSessionView(context);
},
@@ -104,7 +102,6 @@ export const statsCommand: SlashCommand = {
description: 'Show model-specific usage statistics',
kind: CommandKind.BUILT_IN,
autoExecute: true,
isSafeConcurrent: true,
action: (context: CommandContext) => {
const { selectedAuthType, userEmail, tier } = getUserIdentity(context);
const currentModel = context.services.config?.getModel();
@@ -128,7 +125,6 @@ export const statsCommand: SlashCommand = {
description: 'Show tool-specific usage statistics',
kind: CommandKind.BUILT_IN,
autoExecute: true,
isSafeConcurrent: true,
action: (context: CommandContext) => {
context.ui.addItem({
type: MessageType.TOOL_STATS,
@@ -4,7 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { CommandKind, type SlashCommand } from './types.js';
import type { SlashCommand } from './types.js';
import { CommandKind } from './types.js';
import { terminalSetup } from '../utils/terminalSetup.js';
import { type MessageActionReturn } from '@google/gemini-cli-core';
+2 -5
View File
@@ -4,11 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {
CommandKind,
type OpenDialogActionReturn,
type SlashCommand,
} from './types.js';
import type { OpenDialogActionReturn, SlashCommand } from './types.js';
import { CommandKind } from './types.js';
export const themeCommand: SlashCommand = {
name: 'theme',
@@ -4,7 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, type vi } from 'vitest';
import type { vi } from 'vitest';
import { describe, it, expect } from 'vitest';
import { toolsCommand } from './toolsCommand.js';
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
import { MessageType } from '../types.js';

Some files were not shown because too many files have changed in this diff Show More