Compare commits

...

151 Commits

Author SHA1 Message Date
Anjali Sridhar 96f3d6e076 Auto-mutation on improver-experiments-20260319_012229 2026-03-18 18:22:29 -07:00
Muhammad Usman f88488d1f9 fix(core): resolve Windows line ending and path separation bugs across CLI (#21068) 2026-03-09 22:40:22 +00:00
Sehoon Shon 1fd42802be perf(cli): cache loadSettings to reduce redundant disk I/O at startup (#21521) 2026-03-09 21:33:16 +00:00
Tommaso Sciortino ab64b15d51 refactor: migrate to useKeyMatchers hook (#21753) 2026-03-09 20:48:09 +00:00
Jacob Richman e406dcc249 feat/redesign header compact (#20922) 2026-03-09 20:40:46 +00:00
Vroot 680077631d fix(docs): fix headless mode docs (#21287)
Co-authored-by: Sam Roberts <158088236+g-samroberts@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-03-09 20:13:14 +00:00
Jacob Richman 9fc03a0c12 Add script to speed up reviewing PRs adding a worktree. (#21748) 2026-03-09 19:43:22 +00:00
Adib234 4f4431e4e1 fix(core): prevent infinite recursion in symlink resolution (#21750) 2026-03-09 19:38:45 +00:00
AK 527074b50a feat(policy): support subagent-specific policies in TOML (#21431) 2026-03-09 19:22:46 +00:00
joshualitt a17691f0fc feat(core): improve subagent result display (#20378) 2026-03-09 19:20:15 +00:00
Francesco Camporeale d246315cea docs: fix broken markdown syntax and anchor links in /tools (#20902)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Sam Roberts <158088236+g-samroberts@users.noreply.github.com>
2026-03-09 19:03:50 +00:00
Alisa e92ccec6c8 feat(security): implement robust IP validation and safeFetch foundation (#21401) 2026-03-09 19:02:07 +00:00
Pyush Sinha b68d7bc0f9 refactor(cli): better react patterns for BaseSettingsDialog (#21206) 2026-03-09 18:35:08 +00:00
Jacob Richman 4c9f9bb3e2 robustness(core): static checks to validate history is immutable (#21228) 2026-03-09 18:26:03 +00:00
David Pierce e7b20c49ac Update mcp's list function to check for disablement. (#21148) 2026-03-09 18:10:00 +00:00
David Pierce 743d05b37f Unmarshall update (#21721)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-03-09 17:32:15 +00:00
Zheyuan Lin 95074a1a84 fix(settings): display objects as JSON instead of [object Object] (#21458)
Co-authored-by: Zheyuan <zlin252@emory.edu>
Co-authored-by: Jacob Richman <jacob314@gmail.com>
2026-03-09 17:32:13 +00:00
Aditya Bijalwan 759575faa8 Feat/browser agent progress emission (#21218)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-03-09 17:26:00 +00:00
Sehoon Shon d485e08606 feat(cli): implement /upgrade command (#21511) 2026-03-09 17:17:30 +00:00
Sehoon Shon 0f1258305a perf(core): cache loadApiKey to reduce redundant keychain access (#21520) 2026-03-09 16:53:24 +00:00
Abhijit Balaji 09e99824d4 docs: document tool exclusion from memory via deny policy (#21428)
Co-authored-by: Sam Roberts <158088236+g-samroberts@users.noreply.github.com>
2026-03-09 16:29:52 +00:00
joshualitt 96b939f63a feat(core): Introduce AgentLoopContext. (#21198) 2026-03-09 16:02:20 +00:00
Adib234 7837194ab5 fix(core): resolve symlinks for non-existent paths during validation (#21487) 2026-03-09 16:02:13 +00:00
Jerop Kipruto 35ee2a841a feat(plan): enable Plan Mode by default (#21713) 2026-03-09 15:58:46 +00:00
Jack Wotherspoon a253938ac5 feat(cli): add /compact alias for /compress command (#21711) 2026-03-09 15:45:42 +00:00
aworki 37ffd608fd feat(cli): expose /tools desc as explicit subcommand for discoverability (#21241)
Co-authored-by: Coco Sheng <cocosheng@google.com>
Co-authored-by: Gaurav <39389231+gsquared94@users.noreply.github.com>
2026-03-09 15:31:05 +00:00
Chris Williams 0f019122b0 Docs: Make documentation links relative (#21490) 2026-03-09 15:23:00 +00:00
Gaurav 936f6240dd fix(core): sanitize SSE-corrupted JSON and domain strings in error classification (#21702) 2026-03-09 14:08:33 +00:00
anj-s f8dd6f4f4c fix(core): display actual graph output in tracker_visualize tool (#21455) 2026-03-09 13:36:16 +00:00
Gyanranjan Priyam ca7ac00030 docs: fix typo 'allowslisted' -> 'allowlisted' in mcp-server.md (#21665) 2026-03-09 05:38:00 +00:00
Dmitry Lyalin d41735d6a9 feat(cli): unify /chat and /resume command UX (#20256) 2026-03-08 22:50:51 +00:00
Jacob Richman d012929a28 Code review comments as a pr (#21209) 2026-03-08 08:36:54 +00:00
Jenna Inouye 97dfbd4598 Docs: Add documentation for notifications (experimental)(macOS) (#21163) 2026-03-08 06:31:24 +00:00
Keith Guerin 237864eb63 feat(cli): Invert quota language to 'percent used' (#20100)
Co-authored-by: jacob314 <jacob314@gmail.com>
2026-03-07 23:17:10 +00:00
Keith Guerin dc6741097c refactor(cli): standardize on 'reload' verb for all components (#20654)
Co-authored-by: Krishna Korade <MushuEE@users.noreply.github.com>
Co-authored-by: Jacob Richman <jacob314@gmail.com>
2026-03-07 22:56:11 +00:00
Christian Gunderman dac3735626 Disallow underspecified types (#21485) 2026-03-07 21:05:38 +00:00
Jacob Richman 245b68e9f1 Make test suite pass when the GEMINI_SYSTEM_MD env variable or GEMINI_WRITE_SYSTEM_MD variable happens to be set locally/ (#21480) 2026-03-07 20:04:17 +00:00
Jacob Richman e89cf5d86e fix(cli): correct shell height reporting (#21492) 2026-03-07 19:31:09 +00:00
Jarrod Whelan 54b0344fc5 fix(ui): unify Ctrl+O expansion hint experience across buffer modes (#21474) 2026-03-07 19:04:22 +00:00
Keith Guerin e5d58c2b5a feat(cli): overhaul thinking UI (#18725) 2026-03-07 04:20:27 +00:00
Sandy Tao 9455ecd78c fix(core): add in-memory cache to ChatRecordingService to prevent OOM (#21502) 2026-03-07 03:45:36 +00:00
Jeffrey Ying 0fd09e0150 fix: expand paste placeholders in TextInput on submit (#19946)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-03-07 03:29:38 +00:00
Gaurav 9a7427197b fix(billing): fix overage strategy lifecycle and settings integration (#21236) 2026-03-07 03:14:44 +00:00
Arnav Raj 33be30ab04 fix(core): whitelist TERM and COLORTERM in environment sanitization (#20514)
Co-authored-by: Sri Pasumarthi <sripas@google.com>
Co-authored-by: Sri Pasumarthi <111310667+sripasg@users.noreply.github.com>
2026-03-07 02:51:08 +00:00
Mark McLaughlin 6c3a90645a feat(skills): refine string-reviewer guidelines and description (#20368) 2026-03-06 22:46:11 +00:00
Abhi 32e61529f5 feat(cli): hide gemma settings from display and mark as experimental (#21471) 2026-03-06 22:34:59 +00:00
Abhi 931e668b47 refactor(core): standardize MCP tool naming to mcp_ FQN format (#21425) 2026-03-06 22:17:28 +00:00
Emily Hedlund 7989c28d2e feat(cli): implement dot-prefixing for slash command conflicts (#20979) 2026-03-06 20:43:20 +00:00
gemini-cli-robot 2965c115f1 Changelog for v0.33.0-preview.4 (#21354)
Co-authored-by: gemini-cli-robot <224641728+gemini-cli-robot@users.noreply.github.com>
Co-authored-by: Gal Zahavi <38544478+galz10@users.noreply.github.com>
Co-authored-by: galz10 <galzahavi@google.com>
Co-authored-by: Sam Roberts <158088236+g-samroberts@users.noreply.github.com>
2026-03-06 20:36:18 +00:00
Sébastien Prud'homme 7d31d5f4ec fix(core): append correct OTLP paths for HTTP exporters (#16836) 2026-03-06 19:58:00 +00:00
Srinath Padmanabhan 1a7d4184cd Update quota and pricing documentation with subscription tiers (#21351)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Jenna Inouye <jinouye@google.com>
Co-authored-by: Srinath Padmanabhan <srithreepo@google.com>
2026-03-06 19:46:03 +00:00
Sehoon Shon 7dce23e5d9 feat(core): implement generic CacheService and optimize setupUser (#21374) 2026-03-06 19:39:50 +00:00
ruomeng 06a176e33e fix(plan): keep approved plan during chat compression (#21284) 2026-03-06 19:36:05 +00:00
Sandy Tao 42d367d72f fix(cli): gracefully handle --resume when no sessions exist (#21429) 2026-03-06 19:02:33 +00:00
Emily Hedlund 6f579934db feat(core): implement unified KeychainService and migrate token storage (#21344) 2026-03-06 18:56:08 +00:00
Tommaso Sciortino 6d607a5953 feat(ui): dynamically generate all keybinding hints (#21346) 2026-03-06 18:34:26 +00:00
Abhi 4669148a4c feat(core): add concurrency safety guidance for subagent delegation (#17753) (#21278) 2026-03-06 18:09:45 +00:00
nityam 82316ef6e4 fix(core): deduplicate GEMINI.md files by device/inode on case-insensitive filesystems (#19904) (#19915) 2026-03-06 17:52:08 +00:00
Aarchi Kumari 337e4bc8c6 fix(hooks): propagate stopHookActive in AfterAgent retry path (#20426) (#20439) 2026-03-06 17:06:23 +00:00
Horizon_Architect_07 d97eaf3420 Fix(accessibility): add screen reader support to RewindViewer (#20750) 2026-03-06 15:48:36 +00:00
Adam Boudj 0452f787b2 fix(partUtils): display media type and size for inline data parts (#21358) 2026-03-06 15:45:43 +00:00
anj-s c1317aafb3 fix: logic for task tracker strategy and remove tracker tools (#21355) 2026-03-06 15:17:49 +00:00
Mohammed Abdelaziz f17f05373e docs(cli): clarify ! command output visibility in shell commands tutorial (#21041)
Co-authored-by: Sam Roberts <158088236+g-samroberts@users.noreply.github.com>
2026-03-06 08:47:29 +00:00
MumuTW 6691fac50e fix(core): handle AbortError thrown during processTurn (#21296)
Co-authored-by: Gaurav <39389231+gsquared94@users.noreply.github.com>
2026-03-06 06:29:56 +00:00
JATIN 35c1999341 documentiong ensures ripgrep (#21298)
Co-authored-by: Gaurav <39389231+gsquared94@users.noreply.github.com>
2026-03-06 06:10:12 +00:00
kt 5575c5ff66 docs: fix incorrect relative links to command reference (#20964) 2026-03-06 03:27:25 +00:00
Pavan Shinde 0833aca64b docs: format release times as HH:MM UTC (#20726)
Co-authored-by: Sam Roberts <158088236+g-samroberts@users.noreply.github.com>
2026-03-06 03:06:38 +00:00
Sehoon Shon 509d4ae0a9 fix(cli): implement --all flag for extensions uninstall (#21319) 2026-03-06 03:02:01 +00:00
gemini-cli-robot 4d310dda68 Changelog for v0.33.0-preview.3 (#21347)
Co-authored-by: gemini-cli-robot <224641728+gemini-cli-robot@users.noreply.github.com>
Co-authored-by: Gal Zahavi <38544478+galz10@users.noreply.github.com>
Co-authored-by: galz10 <galzahavi@google.com>
2026-03-06 02:11:30 +00:00
gemini-cli-robot e7be0d7bcc Changelog for v0.33.0-preview.2 (#21333)
Co-authored-by: gemini-cli-robot <224641728+gemini-cli-robot@users.noreply.github.com>
2026-03-06 00:35:13 +00:00
anj-s a8f507352b feat (core): Implement tracker related SI changes (#19964) 2026-03-06 00:18:05 +00:00
Mark McLaughlin 6aa6630137 fix(theme): improve theme color contrast for macOS Terminal.app (#21175) 2026-03-05 23:33:49 +00:00
Shreya Keshive 7ec477d40d feat(acp): Add support for AI Gateway auth (#21305) 2026-03-05 22:15:23 +00:00
Tommaso Sciortino 19c9508fd1 remove wildcard behavior on keybindings (#21315) 2026-03-05 22:11:53 +00:00
Jacob Richman e8bc7bea44 Fix code colorizer ansi escape bug. (#21321) 2026-03-05 21:43:13 +00:00
Sam Roberts 1ba9d71284 docs(links): update docs-writer skill and fix broken link (#21314) 2026-03-05 21:20:52 +00:00
Sehoon Shon 1aa4a32066 test(core): improve testing for API request/response parsing (#21227) 2026-03-05 21:00:01 +00:00
Sehoon Shon 22d962e761 feat(core): fallback to 2.5 models with no access for toolcalls (#21283) 2026-03-05 20:37:15 +00:00
Shreya Keshive 0135b03c8a fix(acp): rename --experimental-acp to --acp & remove Zed-specific refrences (#21171) 2026-03-05 19:57:28 +00:00
kartik 9773a084c9 fix:reorder env var redaction checks to scan values first (#21059)
Signed-off-by: Kartik Angiras <angiraskartik@gmail.com>
2026-03-05 19:22:45 +00:00
Juhyuk a830858f91 fix(core): prevent unhandled AbortError crash during stream loop detection (#21123)
Co-authored-by: Gaurav <39389231+gsquared94@users.noreply.github.com>
Co-authored-by: ruomeng <ruomeng@google.com>
2026-03-05 19:12:09 +00:00
Jerop Kipruto f47cb3c136 docs: use absolute paths for internal links in plan-mode.md (#21299) 2026-03-05 19:11:57 +00:00
Zheyuan Lin 291639633f feat(cli): add native gVisor (runsc) sandboxing support (#21062)
Co-authored-by: Zheyuan <zlin252@emory.edu>
Co-authored-by: Kartik Angiras <angiraskartik@gmail.com>
2026-03-05 18:39:57 +00:00
Jacob Richman c7e2dbe0cf Fix so shell calls are formatted (#21237) 2026-03-05 18:39:42 +00:00
Yuna Seol 31d65f40bd feat(telemetry): implement Clearcut logging for startup statistics (#21172)
Co-authored-by: Yuna Seol <yunaseol@google.com>
2026-03-05 18:21:55 +00:00
Sam Roberts 93d9adc03c feat(triage): add area/documentation to issue triage (#21222) 2026-03-05 18:14:47 +00:00
Sam Roberts d2726d4a43 DOCS: Update quota and pricing page (#21194) 2026-03-05 18:09:14 +00:00
Tommaso Sciortino 1c3c7a343f fix(cli): replace hardcoded keybinding strings with dynamic formatters (#21159) 2026-03-05 17:52:01 +00:00
Dev Randalpura f20e45175e fix(ui): add partial output to cancelled shell UI (#21178)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-03-05 17:30:20 +00:00
Sandy Tao 0228c2b9f0 refactor(core): extract shared OAuth flow primitives from MCPOAuthProvider (#20895) 2026-03-05 17:01:37 +00:00
Tommaso Sciortino ddafd79661 fix(cli): fix enabling kitty codes on Windows Terminal (#21136) 2026-03-05 15:29:12 +00:00
Aditya Bijalwan 09259a8ec4 test: add browser agent integration tests (#21151) 2026-03-05 13:29:35 +00:00
Adib234 173376ba67 fix(core): explicitly allow codebase_investigator and cli_help in read-only mode (#21157) 2026-03-05 13:20:04 +00:00
JAYADITYA 013d5dfbb0 refactor(cli): categorize built-in themes into dark/ and light/ directories (#18634) 2026-03-05 09:20:46 +00:00
Darren Sander 3d4956aa57 perf(core): fix OOM crash in long-running sessions (#19608)
Co-authored-by: Jacob Richman <jacob314@gmail.com>
2026-03-05 04:23:07 +00:00
Jack Wotherspoon 9dc6898d28 feat: add custom footer configuration via /footer (#19001)
Co-authored-by: Keith Guerin <keithguerin@gmail.com>
Co-authored-by: Jacob Richman <jacob314@gmail.com>
2026-03-05 02:21:48 +00:00
Christian Gunderman c5112cde46 fix(core): truncate excessively long lines in grep search output (#21147)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-03-05 01:30:28 +00:00
Eric Rahm c72cfad92c fix(cli): defer tool exclusions to policy engine in non-interactive mode (#20639)
Co-authored-by: Bryan Morgan <bryanmorgan@google.com>
2026-03-05 01:01:52 +00:00
Dev Randalpura 205d69eb07 fix(ui): removed double padding on rendered content (#21029) 2026-03-05 01:00:34 +00:00
Gal Zahavi 3481032980 fix(extensions): preserve symlinks in extension source path while enforcing folder trust (#20867) 2026-03-05 00:06:19 +00:00
Gen Zhang a5fd5d0b9f feat(release): ship esbuild bundle in npm package (#19171)
Co-authored-by: Yuna Seol <yunaseol@gmail.com>
2026-03-04 22:18:54 +00:00
Jerop Kipruto 55db3c776c docs: document planning workflows with Conductor example (#21166) 2026-03-04 22:07:05 +00:00
N. Taylor Mullen e63d273e4e fix(ui): suppress redundant failure note when tool error note is shown (#21078) 2026-03-04 21:20:08 +00:00
joshualitt 4c85d14f48 feat(core): Disable fast ack helper for hints. (#21011) 2026-03-04 20:56:56 +00:00
Yashodip More efec63658a chore(cli): enable deprecated settings removal by default (#20682) 2026-03-04 20:39:37 +00:00
Spencer c59ef74837 fix(core, a2a-server): prevent hang during OAuth in non-interactive sessions (#21045) 2026-03-04 20:35:21 +00:00
Jenna Inouye 29b3aa860c Docs: Create tools reference (#19470)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-03-04 20:16:33 +00:00
Christian Gunderman 1c92824636 chore(github): require prompt approvers for agent prompt files (#20896) 2026-03-04 19:45:12 +00:00
Aishanee Shah e200b40408 feat(loop-reduction): implement iterative loop detection and model feedback (#20763) 2026-03-04 19:38:36 +00:00
nityam 8f36051f32 fix: improve error message when OAuth succeeds but project ID is required (#21070) 2026-03-04 19:28:34 +00:00
kartik ac4e65d669 feat: add issue assignee workflow (#21003)
Signed-off-by: Kartik Angiras <angiraskartik@gmail.com>
2026-03-04 19:28:24 +00:00
Yuna Seol 6f3c3c7967 feat(core): add robustness tests, logging, and metrics for CodeAssistServer SSE parsing (#21013)
Co-authored-by: Yuna Seol <yunaseol@google.com>
2026-03-04 19:27:47 +00:00
gemini-cli-robot 212402bd53 Changelog for v0.32.1 (#21055)
Co-authored-by: gemini-cli-robot <224641728+gemini-cli-robot@users.noreply.github.com>
Co-authored-by: Sam Roberts <158088236+g-samroberts@users.noreply.github.com>
2026-03-04 19:20:33 +00:00
Christian Gunderman 49e4082f38 feat(telemetry): include language in telemetry and fix accepted lines computation (#21126) 2026-03-04 18:58:39 +00:00
Christian Gunderman 54885214a1 feat(evals): add overall pass rate row to eval nightly summary table (#20905) 2026-03-04 18:58:18 +00:00
Himanshu Soni 717660997d feat(sandbox): add experimental LXC container sandbox support (#20735) 2026-03-04 17:44:33 +00:00
Sehoon Shon bc89b05f01 feat(core): improve @file autocomplete to prioritize filenames (#21064) 2026-03-04 17:24:34 +00:00
gemini-cli-robot 66721379f8 Changelog for v0.33.0-preview.1 (#21058)
Co-authored-by: gemini-cli-robot <224641728+gemini-cli-robot@users.noreply.github.com>
2026-03-04 17:17:00 +00:00
gemini-cli-robot df74b89876 Changelog for v0.32.0 (#21033)
Co-authored-by: gemini-cli-robot <224641728+gemini-cli-robot@users.noreply.github.com>
2026-03-04 17:15:52 +00:00
Rudra 25e9613594 fix(cli): enable multi-arch docker builds for sandbox (#19821)
Co-authored-by: Tommaso Sciortino <sciortino@gmail.com>
2026-03-04 16:53:30 +00:00
Rohit 94ba7ec319 chore(dev): update vscode settings for typescriptreact (#19907) 2026-03-04 16:25:33 +00:00
Eli Fayerman 07d2187a76 fix(cli): register extension lifecycle events in DebugProfiler (#20101)
Co-authored-by: Adib234 <30782825+Adib234@users.noreply.github.com>
2026-03-04 16:03:00 +00:00
Jacob Richman 352fb0c976 fix(mcp): Notifications/tools/list_changed support not working (#21050)
Co-authored-by: Bryan Morgan <bryanmorgan@google.com>
2026-03-04 09:46:17 -05:00
Jenna Inouye 7011c13ee6 Docs: Update plan mode docs (#19682)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Jerop Kipruto <jerop@google.com>
2026-03-04 12:53:51 +00:00
Jacob Richman 12957ea16a fix(cli): Polish shell autocomplete rendering to be a little more shell native feeling. (#20931) 2026-03-04 06:52:56 +00:00
Jacob Richman 1017b78157 Defensive coding to reduce the risk of Maximum update depth errors (#20940) 2026-03-04 06:32:05 +00:00
Tommaso Sciortino 7e06559db2 Consistently guard restarts against concurrent auto updates (#21016) 2026-03-04 06:18:12 +00:00
gemini-cli-robot bbcfff5cf1 chore/release: bump version to 0.34.0-nightly.20260304.28af4e127 (#21054) 2026-03-04 05:06:31 +00:00
Sri Pasumarthi 5bd21a240d fix: model persistence for all scenarios (#21051) 2026-03-04 03:28:12 +00:00
gemini-cli-robot fdfd626f28 Changelog for v0.33.0-preview.0 (#21030)
Co-authored-by: gemini-cli-robot <224641728+gemini-cli-robot@users.noreply.github.com>
2026-03-04 02:51:50 +00:00
Jacob Richman 0659ad1702 Revert "fix(ui): persist expansion in AskUser dialog when navigating options" (#21042) 2026-03-04 01:40:29 +00:00
anj-s a63c76522a feat(core): Add tracker CRUD tools & visualization (#19489)
Co-authored-by: Jerop Kipruto <jerop@google.com>
2026-03-04 00:42:48 +00:00
Jacob Richman af424aefa9 Add extra safety checks for proto pollution (#20396)
Co-authored-by: Gal Zahavi <38544478+galz10@users.noreply.github.com>
2026-03-04 00:21:09 +00:00
nityam 28af4e127f fix: merge duplicate imports in packages/core (3/4) (#20928) 2026-03-04 00:12:59 +00:00
Keith Guerin d25088956d feat(ui): standardize semantic focus colors and enhance history visibility (#20745)
Co-authored-by: jacob314 <jacob314@gmail.com>
2026-03-04 00:10:09 +00:00
gemini-cli-robot 75737c1b44 chore(release): bump version to 0.34.0-nightly.20260303.34f0c1538 (#21034) 2026-03-04 00:08:34 +00:00
Jacob Richman 533b65188d Cleanup old branches. (#19354) 2026-03-03 23:01:06 +00:00
Adam Weidman 066b0cc15f fix(core): increase A2A agent timeout to 30 minutes (#21028) 2026-03-03 22:59:53 +00:00
Jacob Richman 1124e49f34 Support bold and other styles in svg snapshots (#20937) 2026-03-03 22:44:25 +00:00
Shashank Trivedi df14a6c2db feat(cli): add chat resume footer on session quit (#20667)
Co-authored-by: Dev Randalpura <devrandalpura@google.com>
2026-03-03 22:38:26 +00:00
Shreya Keshive 34f0c1538b feat(acp): add set models interface (#20991) 2026-03-03 22:29:42 +00:00
Sehoon Shon c70c95ead3 remove hardcoded tiername when missing tier (#21022) 2026-03-03 22:16:37 +00:00
Dev Randalpura f3bbe6e77a fix(core): send shell output to model on cancel (#20501) 2026-03-03 22:10:16 +00:00
Abhi 28e79831ac fix(core): sanitize and length-check MCP tool qualified names (#20987) 2026-03-03 21:38:52 +00:00
Sam Roberts fdf5b18cfb Format the quota/limit style guide. (#21017) 2026-03-03 21:37:05 +00:00
ruomeng b5f3eb2c9c feat(plan): add copy subcommand to plan (#20491) (#20988) 2026-03-03 21:36:51 +00:00
Ale Aadithya 2a84090dd5 Docs/add hooks reference (#20961)
Co-authored-by: Sam Roberts <158088236+g-samroberts@users.noreply.github.com>
2026-03-03 21:29:15 +00:00
Sri Pasumarthi 27d7aeb1ed feat: Implement slash command handling in ACP for /memory,/init,/extensions and /restore (#20528) 2026-03-03 21:29:14 +00:00
Tommaso Sciortino d6c560498b fix(cli): pin clipboardy to ~5.2.x (#21009) 2026-03-03 21:01:29 +00:00
Sam Roberts 4500da339b Update docs-writer skill with new resource (#20917) 2026-03-03 20:49:18 +00:00
Ishaan Gupta 4be08a2261 refactor common settings logic for skills,agents (#17490)
Co-authored-by: ved015 <vedant.04.mahajan@gmail.com>
Co-authored-by: Tommaso Sciortino <sciortino@gmail.com>
2026-03-03 19:25:17 +00:00
747 changed files with 31245 additions and 11361 deletions
+8 -1
View File
@@ -45,6 +45,10 @@ Write precisely to ensure your instructions are unambiguous.
specific verbs.
- **Examples:** Use meaningful names in examples; avoid placeholders like
"foo" or "bar."
- **Quota and limit terminology:** For any content involving resource capacity
or using the word "quota" or "limit", strictly adhere to the guidelines in
the `quota-limit-style-guide.md` resource file. Generally, Use "quota" for the
administrative bucket and "limit" for the numerical ceiling.
### Formatting and syntax
Apply consistent formatting to make documentation visually organized and
@@ -114,6 +118,8 @@ documentation.
reflects existing code.
- **Structure:** Apply "Structure (New Docs)" rules (BLUF, headings, etc.) when
adding new sections to existing pages.
- **Headers**: If you change a header, you must check for links that lead to
that header and update them.
- **Tone:** Ensure the tone is active and engaging. Use "you" and contractions.
- **Clarity:** Correct awkward wording, spelling, and grammar. Rephrase
sentences to make them easier for users to understand.
@@ -129,7 +135,8 @@ and that all links are functional.
technical behavior.
2. **Self-review:** Re-read changes for formatting, correctness, and flow.
3. **Link check:** Verify all new and existing links leading to or from modified
pages.
pages. If you changed a header, ensure that any links that lead to it are
updated.
4. **Format:** Once all changes are complete, ask to execute `npm run format`
to ensure consistent formatting across the project. If the user confirms,
execute the command.
@@ -0,0 +1,61 @@
# Style Guide: Quota vs. Limit
This guide defines the usage of "quota," "limit," and related terms in
user-facing interfaces.
## TL;DR
- **`quota`**: The administrative "bucket." Use for settings, billing, and
requesting increases. (e.g., "Adjust your storage **quota**.")
- **`limit`**: The real-time numerical "ceiling." Use for error messages when a
user is blocked. (e.g., "You've reached your request **limit**.")
- **When blocked, combine them:** Explain the **limit** that was hit and the
**quota** that is the remedy. (e.g., "You've reached the request **limit** for
your developer **quota**.")
- **Related terms:** Use `usage` for consumption tracking, `restriction` for
fixed rules, and `reset` for when a limit refreshes.
---
## Detailed Guidelines
### Definitions
- **Quota is the "what":** It identifies the category of resource being managed
(e.g., storage quota, GPU quota, request/prompt quota).
- **Limit is the "how much":** It defines the numerical boundary.
Use **quota** when referring to the administrative concept or the request for
more. Use **limit** when discussing the specific point of exhaustion.
### When to use "quota"
Use this term for **account management, billing, and settings.** It describes
the entitlement the user has purchased or been assigned.
**Examples:**
- **Navigation label:** Quota and usage
- **Contextual help:** Your **usage quota** is managed by your organization. To
request an increase, contact your administrator.
### When to use "limit"
Use this term for **real-time feedback, notifications, and error messages.** It
identifies the specific wall the user just hit.
**Examples:**
- **Error message:** Youve reached the 50-request-per-minute **limit**.
- **Inline warning:** Input exceeds the 32k token **limit**.
### How to use both together
When a user is blocked, combine both terms to explain the **event** (limit) and
the **remedy** (quota).
**Example:**
- **Heading:** Daily usage limit reached
- **Body:** You've reached the maximum daily capacity for your developer quota.
To continue working today, upgrade your quota.
+99
View File
@@ -0,0 +1,99 @@
---
name: string-reviewer
description: >
Use this skill when asked to review text and user-facing strings within the codebase. It ensures that these strings follow rules on clarity,
usefulness, brevity and style.
---
# String Reviewer
## Instructions
Act as a Senior UX Writer. Look for user-facing strings that are too long,
unclear, or inconsistent. This includes inline text, error messages, and other
user-facing text.
Do NOT automatically change strings without user approval. You must only suggest
changes and do not attempt to rewrite them directly unless the user explicitly
asks you to do so.
## Core voice principles
The system prioritizes deterministic clarity over conversational fluff. We
provide telemetry, not etiquette, ensuring the user retains absolute agency..
1. **Deterministic clarity:** Distinguish between certain system/service states
(Cloud Billing, IAM, the System) and probabilistic AI analysis (Gemini).
2. **System transparency:** Replace "Loading..." with active technical telemetry
(e.g., Tracing stack traces...). Keep status updates under 5 words.
3. **Front-loaded actionability:** Always use the [Goal] + [Action] pattern.
Lead with intent so users can scan left-to-right.
4. **Agentic error recovery:** Every error must be a pivot point. Pair failures
with one-click recovery commands or suggested prompts.
5. **Contextual humility:** Reserve disclaimers and "be careful" warnings for P0
(destructive/irreversible) tasks only. Stop warning-fatigue.
## The writing checklist
Use this checklist to audit UI strings and AI responses.
### Identity and voice
- **Eliminate the "I":** Remove all first-person pronouns (I, me, my, mine).
- **Subject attribution:** Refer to the AI as Gemini and the infrastructure as
the - system or the CLI.
- **Active voice:** Ensure the subject (Gemini or the system) is clearly
performing the action.
- **Ownership rule:** Use the system for execution (doing) and Gemini for
analysis (thinking)
### Structural scannability
- **The skip test:** Do the first 3 words describe the users intent? If not,
rewrite.
- **Goal-first sequence:** Use the template: [To Accomplish X] + [Do Y].
- **The 5-word rule:** Keep status updates and loading states under 5 words.
- **Telemetry over etiquette:** Remove polite filler (Please wait, Thank you,
Certainly). Replace with raw data or progress indicators.
- **Micro-state cycles:** For tasks $> 3$ seconds, cycle through specific
sub-states (e.g., Parsing logs... ➔ Identifying patterns...) to show momentum.
### Technical accuracy and humility
- **Verb signal check:** Use deterministic verbs (is, will, must) for system
state/infrastructure.
- Use probabilistic verbs (suggests, appears, may, identifies) for AI output.
- **No 100% certainty:** Never attribute absolute certainty to model-generated
content.
- **Precision over fuzziness:** Use technical metrics (latency, tokens, compute) instead of "speed" or "cost."
- **Instructional warnings:** Every warning must include a specific corrective action (e.g., "Perform a dry-run first" or "Review line 42").
### Agentic error recovery
- **The one-step rule:** Pair every error message with exactly one immediate
path to a fix (command, link, or prompt).
- **Human-first:** Provide a human-readable explanation before machine error
codes (e.g., 404, 500).
- **Suggested prompts:** Offer specific text for the user to copy/click like
“Ask Gemini: 'Explain this port error.'”
### Use consistent terminology
Ensure all terminology aligns with the project [word
list](./references/word-list.md).
If a string uses a term marked "do not use" or "use with caution," provide a
correction based on the preferred terms.
## Ensure consistent style for settings
If `packages/cli/src/config/settingsSchema.ts` is modified, confirm labels and
descriptions specifically follow the unique [Settings
guidelines](./references/settings.md).
## Output format
When suggesting changes, always present your review using the following list
format. Do not provide suggestions outside of this list..
```
1. **{Rationale/Principle Violated}**
- ❌ "{incorrect phrase}"
- ✅ `"{corrected phrase}"`
```
@@ -0,0 +1,28 @@
# Settings
## Noun-First Labeling (Scannability)
Labels must start with the subject of the setting, not the action. This allows
users to scan for the feature they want to change.
- **Rule:** `[Noun]` `[Attribute/Action]`
- **Example:** `Show line numbers` becomes simply `Line numbers`
## Positive Boolean Logic (Cognitive Ease)
Eliminate "double negatives." Booleans should represent the presence of a
feature, not its absence.
- **Rule:** Replace `Disable {feature}` or `Hide {Feature}` with
`{Feature} enabled` or simply `{Feature}`.
- **Example:** Change "Disable auto update" to "Auto update".
- **Implementation:** Invert the boolean value in your config loader so true
always equals `On`
## Verb Stripping (Brevity)
Remove redundant leading verbs like "Enable," "Use," "Display," or "Show" unless
they are part of a specific technical term.
- **Rule**: If the label works without the verb, remove it
- **Example**: Change `Enable prompt completion` to `Prompt completion`
@@ -0,0 +1,61 @@
## Terms
### Preferred
- Use **create** when a user is creating or setting up something.
- Use **allow** instead of **may** to indicate that permission has been granted
to perform some action.
- Use **canceled**, not **cancelled**.
- Use **configure** to refer to the process of changing the attributes of a
feature, even if that includes turning on or off the feature.
- Use **delete** when the action being performed is destructive.
- Use **enable** for binary operations that turn a feature or API on. Use "turn
on" and "turn off" instead of "enable" and "disable" for other situations.
- Use **key combination** to refer to pressing multiple keys simultaneously.
- Use **key sequence** to refer to pressing multiple keys separately in order.
- Use **modify** to refer to something that has changed vs obtaining the latest
version of something.
- Use **remove** when the action being performed takes an item out of a larger
whole, but doesn't destroy the item itself.
- Use **set up** as a verb. Use **setup** as a noun or adjective.
- Use **show**. In general, use paired with **hide**.
- Use **sign in**, **sign out** as a verb. Use **sign-in** or **sign-out** as a
noun or adjective.
- Use **update** when you mean to obtain the latest version of something.
- Use **want** instead of **like** or **would like**.
#### Don't use
- Don't use **etc.** It's redundant. To convey that a series is incomplete,
introduce it with "such as" instead.
- Don't use **hostname**, use "host name" instead.
- Don't use **in order to**. It's too formal. "Before you can" is usually better
in UI text.
- Don't use **one or more**. Specify the quantity where possible. Use "at least
one" when the quantity is 1+ but you can't be sure of the number. Likewise,
use "at least one" when the user must choose a quantity of 1+.
- Don't use the terms **log in**, **log on**, **login**, **logout** or **log
out**.
- Don't use **like** or **would you like**. Use **want** instead. Better yet,
rephrase so that it's not referring to the user's emotional state, but rather
what is required.
#### Use with caution
- Avoid using **leverage**, especially as a verb. "Leverage" is considered a
buzzword largely devoid of meaning apart from the simpler "use".
- Avoid using **once** as a synonym for "after". Typically, when "once" is used
in this way, it is followed by a verb in the perfect tense.
- Don't use **e.g.** Use "example", "such as", "like", or "for example". The
phrase is always followed by a comma.
- Don't use **i.e.** unless absolutely essential to make text fit. Use "that is"
instead.
- Use **disable** for binary operations that turn a feature or API off. Use
"turn on" and "turn off" instead of "enable" and "disable" for other
situations. For UI elements that are not available, use "dimmed" instead of
"disabled".
- Use **please** only when you're asking the user to do something inconvenient,
not just following the instructions in a typical flow.
- Use **really** sparingly in such constructions as "Do you really want to..."
Because of the weight it puts on the decision, it should be used to confirm
actions that the user is extremely unlikely to make.
+6 -1
View File
@@ -14,4 +14,9 @@
# Docs have a dedicated approver group in addition to maintainers
/docs/ @google-gemini/gemini-cli-maintainers @google-gemini/gemini-cli-docs
/README.md @google-gemini/gemini-cli-maintainers @google-gemini/gemini-cli-docs
/README.md @google-gemini/gemini-cli-maintainers @google-gemini/gemini-cli-docs
# Prompt contents, tool definitions, and evals require reviews from prompt approvers
/packages/core/src/prompts/ @google-gemini/gemini-cli-prompt-approvers
/packages/core/src/tools/ @google-gemini/gemini-cli-prompt-approvers
/evals/ @google-gemini/gemini-cli-prompt-approvers
@@ -192,6 +192,13 @@ runs:
INPUTS_CLI_PACKAGE_NAME: '${{ inputs.cli-package-name }}'
INPUTS_A2A_PACKAGE_NAME: '${{ inputs.a2a-package-name }}'
- name: '📦 Prepare bundled CLI for npm release'
if: "inputs.npm-registry-url != 'https://npm.pkg.github.com/'"
working-directory: '${{ inputs.working-directory }}'
shell: 'bash'
run: |
node ${{ github.workspace }}/scripts/prepare-npm-release.js
- name: 'Get CLI Token'
uses: './.github/actions/npm-auth-token'
id: 'cli-token'
+13 -4
View File
@@ -44,6 +44,8 @@ runs:
- name: 'npm build'
shell: 'bash'
run: 'npm run build'
- name: 'Set up QEMU'
uses: 'docker/setup-qemu-action@v3'
- name: 'Set up Docker Buildx'
uses: 'docker/setup-buildx-action@v3'
- name: 'Log in to GitHub Container Registry'
@@ -69,16 +71,19 @@ runs:
env:
INPUTS_GITHUB_REF_NAME: '${{ inputs.github-ref-name }}'
INPUTS_GITHUB_SHA: '${{ inputs.github-sha }}'
# We build amd64 just so we can verify it.
# We build and push both amd64 and arm64 in the publish step.
- name: 'build'
id: 'docker_build'
shell: 'bash'
env:
GEMINI_SANDBOX_IMAGE_TAG: '${{ steps.image_tag.outputs.FINAL_TAG }}'
GEMINI_SANDBOX: 'docker'
BUILD_SANDBOX_FLAGS: '--platform linux/amd64 --load'
STEPS_IMAGE_TAG_OUTPUTS_FINAL_TAG: '${{ steps.image_tag.outputs.FINAL_TAG }}'
run: |-
npm run build:sandbox -- \
--image google/gemini-cli-sandbox:${STEPS_IMAGE_TAG_OUTPUTS_FINAL_TAG} \
--image "google/gemini-cli-sandbox:${STEPS_IMAGE_TAG_OUTPUTS_FINAL_TAG}" \
--output-file final_image_uri.txt
echo "uri=$(cat final_image_uri.txt)" >> $GITHUB_OUTPUT
- name: 'verify'
@@ -92,10 +97,14 @@ runs:
- name: 'publish'
shell: 'bash'
if: "${{ inputs.dry-run != 'true' }}"
run: |-
docker push "${STEPS_DOCKER_BUILD_OUTPUTS_URI}"
env:
STEPS_DOCKER_BUILD_OUTPUTS_URI: '${{ steps.docker_build.outputs.uri }}'
GEMINI_SANDBOX_IMAGE_TAG: '${{ steps.image_tag.outputs.FINAL_TAG }}'
GEMINI_SANDBOX: 'docker'
BUILD_SANDBOX_FLAGS: '--platform linux/amd64,linux/arm64 --push'
STEPS_IMAGE_TAG_OUTPUTS_FINAL_TAG: '${{ steps.image_tag.outputs.FINAL_TAG }}'
run: |-
npm run build:sandbox -- \
--image "google/gemini-cli-sandbox:${STEPS_IMAGE_TAG_OUTPUTS_FINAL_TAG}"
- name: 'Create issue on failure'
if: |-
${{ failure() }}
+21
View File
@@ -264,6 +264,27 @@ jobs:
run: 'npm run build'
shell: 'pwsh'
- name: 'Ensure Chrome is available'
shell: 'pwsh'
run: |
$chromePaths = @(
"${env:ProgramFiles}\Google\Chrome\Application\chrome.exe",
"${env:ProgramFiles(x86)}\Google\Chrome\Application\chrome.exe"
)
$chromeExists = $chromePaths | Where-Object { Test-Path $_ } | Select-Object -First 1
if (-not $chromeExists) {
Write-Host 'Chrome not found, installing via Chocolatey...'
choco install googlechrome -y --no-progress --ignore-checksums
}
$installed = $chromePaths | Where-Object { Test-Path $_ } | Select-Object -First 1
if ($installed) {
Write-Host "Chrome found at: $installed"
& $installed --version
} else {
Write-Error 'Chrome installation failed'
exit 1
}
- name: 'Run E2E tests'
env:
GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}'
+1 -1
View File
@@ -169,7 +169,7 @@ jobs:
npm run test:ci --workspace @google/gemini-cli
else
# Explicitly list non-cli packages to ensure they are sharded correctly
npm run test:ci --workspace @google/gemini-cli-core --workspace @google/gemini-cli-a2a-server --workspace gemini-cli-vscode-ide-companion --workspace @google/gemini-cli-test-utils --if-present
npm run test:ci --workspace @google/gemini-cli-core --workspace @google/gemini-cli-a2a-server --workspace gemini-cli-vscode-ide-companion --workspace @google/gemini-cli-test-utils --if-present -- --coverage.enabled=false
npm run test:scripts
fi
@@ -121,6 +121,7 @@ jobs:
'area/security',
'area/platform',
'area/extensions',
'area/documentation',
'area/unknown'
];
const labelNames = labels.map(label => label.name).filter(name => allowedLabels.includes(name));
@@ -255,6 +256,14 @@ jobs:
"Issues with a specific extension."
"Feature request for the extension ecosystem."
area/documentation
- Description: Issues related to user-facing documentation and other content on the documentation website.
- Example Issues:
"A typo in a README file."
"DOCS: A command is not working as described in the documentation."
"A request for a new documentation page."
"Instructions missing for skills feature"
area/unknown
- Description: Issues that do not clearly fit into any other defined area/ category, or where information is too limited to make a determination. Use this when no other area is appropriate.
@@ -63,7 +63,7 @@ jobs:
echo '🔍 Finding issues missing area labels...'
NO_AREA_ISSUES="$(gh issue list --repo "${GITHUB_REPOSITORY}" \
--search 'is:open is:issue -label:area/core -label:area/agent -label:area/enterprise -label:area/non-interactive -label:area/security -label:area/platform -label:area/extensions -label:area/unknown' --limit 100 --json number,title,body)"
--search 'is:open is:issue -label:area/core -label:area/agent -label:area/enterprise -label:area/non-interactive -label:area/security -label:area/platform -label:area/extensions -label:area/documentation -label:area/unknown' --limit 100 --json number,title,body)"
echo '🔍 Finding issues missing kind labels...'
NO_KIND_ISSUES="$(gh issue list --repo "${GITHUB_REPOSITORY}" \
@@ -204,6 +204,7 @@ jobs:
Categorization Guidelines (Area):
area/agent: Core Agent, Tools, Memory, Sub-Agents, Hooks, Agent Quality
area/core: User Interface, OS Support, Core Functionality
area/documentation: End-user and contributor-facing documentation, website-related
area/enterprise: Telemetry, Policy, Quota / Licensing
area/extensions: Gemini CLI extensions capability
area/non-interactive: GitHub Actions, SDK, 3P Integrations, Shell Scripting, Command line automation
@@ -0,0 +1,315 @@
name: 'Unassign Inactive Issue Assignees'
# This workflow runs daily and scans every open "help wanted" issue that has
# one or more assignees. For each assignee it checks whether they have a
# non-draft pull request (open and ready for review, or already merged) that
# is linked to the issue. Draft PRs are intentionally excluded so that
# contributors cannot reset the check by opening a no-op PR. If no
# qualifying PR is found within 7 days of assignment the assignee is
# automatically removed and a friendly comment is posted so that other
# contributors can pick up the work.
# Maintainers, org members, and collaborators (anyone with write access or
# above) are always exempted and will never be auto-unassigned.
on:
schedule:
- cron: '0 9 * * *' # Every day at 09:00 UTC
workflow_dispatch:
inputs:
dry_run:
description: 'Run in dry-run mode (no changes will be applied)'
required: false
default: false
type: 'boolean'
concurrency:
group: '${{ github.workflow }}'
cancel-in-progress: true
defaults:
run:
shell: 'bash'
jobs:
unassign-inactive-assignees:
if: "github.repository == 'google-gemini/gemini-cli'"
runs-on: 'ubuntu-latest'
permissions:
issues: 'write'
steps:
- name: 'Generate GitHub App Token'
id: 'generate_token'
uses: 'actions/create-github-app-token@v2'
with:
app-id: '${{ secrets.APP_ID }}'
private-key: '${{ secrets.PRIVATE_KEY }}'
- name: 'Unassign inactive assignees'
uses: 'actions/github-script@v7'
env:
DRY_RUN: '${{ inputs.dry_run }}'
with:
github-token: '${{ steps.generate_token.outputs.token }}'
script: |
const dryRun = process.env.DRY_RUN === 'true';
if (dryRun) {
core.info('DRY RUN MODE ENABLED: No changes will be applied.');
}
const owner = context.repo.owner;
const repo = context.repo.repo;
const GRACE_PERIOD_DAYS = 7;
const now = new Date();
let maintainerLogins = new Set();
const teams = ['gemini-cli-maintainers', 'gemini-cli-askmode-approvers', 'gemini-cli-docs'];
for (const team_slug of teams) {
try {
const members = await github.paginate(github.rest.teams.listMembersInOrg, {
org: owner,
team_slug,
});
for (const m of members) maintainerLogins.add(m.login.toLowerCase());
core.info(`Fetched ${members.length} members from team ${team_slug}.`);
} catch (e) {
core.warning(`Could not fetch team ${team_slug}: ${e.message}`);
}
}
const isGooglerCache = new Map();
const isGoogler = async (login) => {
if (isGooglerCache.has(login)) return isGooglerCache.get(login);
try {
for (const org of ['googlers', 'google']) {
try {
await github.rest.orgs.checkMembershipForUser({ org, username: login });
isGooglerCache.set(login, true);
return true;
} catch (e) {
if (e.status !== 404) throw e;
}
}
} catch (e) {
core.warning(`Could not check org membership for ${login}: ${e.message}`);
}
isGooglerCache.set(login, false);
return false;
};
const permissionCache = new Map();
const isPrivilegedUser = async (login) => {
if (maintainerLogins.has(login.toLowerCase())) return true;
if (permissionCache.has(login)) return permissionCache.get(login);
try {
const { data } = await github.rest.repos.getCollaboratorPermissionLevel({
owner,
repo,
username: login,
});
const privileged = ['admin', 'maintain', 'write', 'triage'].includes(data.permission);
permissionCache.set(login, privileged);
if (privileged) {
core.info(` @${login} is a repo collaborator (${data.permission}) — exempt.`);
return true;
}
} catch (e) {
if (e.status !== 404) {
core.warning(`Could not check permission for ${login}: ${e.message}`);
}
}
const googler = await isGoogler(login);
permissionCache.set(login, googler);
return googler;
};
core.info('Fetching open "help wanted" issues with assignees...');
const issues = await github.paginate(github.rest.issues.listForRepo, {
owner,
repo,
state: 'open',
labels: 'help wanted',
per_page: 100,
});
const assignedIssues = issues.filter(
(issue) => !issue.pull_request && issue.assignees && issue.assignees.length > 0
);
core.info(`Found ${assignedIssues.length} assigned "help wanted" issues.`);
let totalUnassigned = 0;
let timelineEvents = [];
try {
timelineEvents = await github.paginate(github.rest.issues.listEventsForTimeline, {
owner,
repo,
issue_number: issue.number,
per_page: 100,
mediaType: { previews: ['mockingbird'] },
});
} catch (err) {
core.warning(`Could not fetch timeline for issue #${issue.number}: ${err.message}`);
continue;
}
const assignedAtMap = new Map();
for (const event of timelineEvents) {
if (event.event === 'assigned' && event.assignee) {
const login = event.assignee.login.toLowerCase();
const at = new Date(event.created_at);
assignedAtMap.set(login, at);
} else if (event.event === 'unassigned' && event.assignee) {
assignedAtMap.delete(event.assignee.login.toLowerCase());
}
}
const linkedPRAuthorSet = new Set();
const seenPRKeys = new Set();
for (const event of timelineEvents) {
if (
event.event !== 'cross-referenced' ||
!event.source ||
event.source.type !== 'pull_request' ||
!event.source.issue ||
!event.source.issue.user ||
!event.source.issue.number ||
!event.source.issue.repository
) continue;
const prOwner = event.source.issue.repository.owner.login;
const prRepo = event.source.issue.repository.name;
const prNumber = event.source.issue.number;
const prAuthor = event.source.issue.user.login.toLowerCase();
const prKey = `${prOwner}/${prRepo}#${prNumber}`;
if (seenPRKeys.has(prKey)) continue;
seenPRKeys.add(prKey);
try {
const { data: pr } = await github.rest.pulls.get({
owner: prOwner,
repo: prRepo,
pull_number: prNumber,
});
const isReady = (pr.state === 'open' && !pr.draft) ||
(pr.state === 'closed' && pr.merged_at !== null);
core.info(
` PR ${prKey} by @${prAuthor}: ` +
`state=${pr.state}, draft=${pr.draft}, merged=${!!pr.merged_at} → ` +
(isReady ? 'qualifies' : 'does NOT qualify (draft or closed without merge)')
);
if (isReady) linkedPRAuthorSet.add(prAuthor);
} catch (err) {
core.warning(`Could not fetch PR ${prKey}: ${err.message}`);
}
}
const assigneesToRemove = [];
for (const assignee of issue.assignees) {
const login = assignee.login.toLowerCase();
if (await isPrivilegedUser(assignee.login)) {
core.info(` @${assignee.login}: privileged user — skipping.`);
continue;
}
const assignedAt = assignedAtMap.get(login);
if (!assignedAt) {
core.warning(
`No 'assigned' event found for @${login} on issue #${issue.number}; ` +
`falling back to issue creation date (${issue.created_at}).`
);
assignedAtMap.set(login, new Date(issue.created_at));
}
const resolvedAssignedAt = assignedAtMap.get(login);
const daysSinceAssignment = (now - resolvedAssignedAt) / (1000 * 60 * 60 * 24);
core.info(
` @${login}: assigned ${daysSinceAssignment.toFixed(1)} day(s) ago, ` +
`ready-for-review PR: ${linkedPRAuthorSet.has(login) ? 'yes' : 'no'}`
);
if (daysSinceAssignment < GRACE_PERIOD_DAYS) {
core.info(` → within grace period, skipping.`);
continue;
}
if (linkedPRAuthorSet.has(login)) {
core.info(` → ready-for-review PR found, keeping assignment.`);
continue;
}
core.info(` → no ready-for-review PR after ${GRACE_PERIOD_DAYS} days, will unassign.`);
assigneesToRemove.push(assignee.login);
}
if (assigneesToRemove.length === 0) {
continue;
}
if (!dryRun) {
try {
await github.rest.issues.removeAssignees({
owner,
repo,
issue_number: issue.number,
assignees: assigneesToRemove,
});
} catch (err) {
core.warning(
`Failed to unassign ${assigneesToRemove.join(', ')} from issue #${issue.number}: ${err.message}`
);
continue;
}
const mentionList = assigneesToRemove.map((l) => `@${l}`).join(', ');
const commentBody =
`👋 ${mentionList} — it has been more than ${GRACE_PERIOD_DAYS} days since ` +
`you were assigned to this issue and we could not find a pull request ` +
`ready for review.\n\n` +
`To keep the backlog moving and ensure issues stay accessible to all ` +
`contributors, we require a PR that is open and ready for review (not a ` +
`draft) within ${GRACE_PERIOD_DAYS} days of assignment.\n\n` +
`We are automatically unassigning you so that other contributors can pick ` +
`this up. If you are still actively working on this, please:\n` +
`1. Re-assign yourself by commenting \`/assign\`.\n` +
`2. Open a PR (not a draft) linked to this issue (e.g. \`Fixes #${issue.number}\`) ` +
`within ${GRACE_PERIOD_DAYS} days so the automation knows real progress is being made.\n\n` +
`Thank you for your contribution — we hope to see a PR from you soon! 🙏`;
try {
await github.rest.issues.createComment({
owner,
repo,
issue_number: issue.number,
body: commentBody,
});
} catch (err) {
core.warning(
`Failed to post comment on issue #${issue.number}: ${err.message}`
);
}
}
totalUnassigned += assigneesToRemove.length;
core.info(
` ${dryRun ? '[DRY RUN] Would have unassigned' : 'Unassigned'}: ${assigneesToRemove.join(', ')}`
);
}
core.info(`\nDone. Total assignees ${dryRun ? 'that would be' : ''} unassigned: ${totalUnassigned}`);
+3
View File
@@ -7,6 +7,9 @@
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
+38 -18
View File
@@ -60,20 +60,41 @@ All submissions, including submissions by project members, require review. We
use [GitHub pull requests](https://docs.github.com/articles/about-pull-requests)
for this purpose.
If your pull request involves changes to `packages/cli` (the frontend), we
recommend running our automated frontend review tool. **Note: This tool is
currently experimental.** It helps detect common React anti-patterns, testing
issues, and other frontend-specific best practices that are easy to miss.
To assist with the review process, we provide an automated review tool that
helps detect common anti-patterns, testing issues, and other best practices that
are easy to miss.
To run the review tool, enter the following command from within Gemini CLI:
#### Using the automated review tool
```text
/review-frontend <PR_NUMBER>
```
You can run the review tool in two ways:
Replace `<PR_NUMBER>` with your pull request number. Authors are encouraged to
run this on their own PRs for self-review, and reviewers should use it to
augment their manual review process.
1. **Using the helper script (Recommended):** We provide a script that
automatically handles checking out the PR into a separate worktree,
installing dependencies, building the project, and launching the review
tool.
```bash
./scripts/review.sh <PR_NUMBER> [model]
```
**Authors are strongly encouraged to run this script on their own PRs**
immediately after creation. This allows you to catch and fix simple issues
locally before a maintainer performs a full review.
**Note on Models:** By default, the script uses the latest Pro model
(`gemini-3.1-pro-preview`). If you do not have enough Pro quota, you can run
it with the latest Flash model instead:
`./scripts/review.sh <PR_NUMBER> gemini-3-flash-preview`.
2. **Manually from within Gemini CLI:** If you already have the PR checked out
and built, you can run the tool directly from the CLI prompt:
```text
/review-frontend <PR_NUMBER>
```
Replace `<PR_NUMBER>` with your pull request number. Reviewers should use this
tool to augment, not replace, their manual review process.
### Self-assigning and unassigning issues
@@ -267,7 +288,8 @@ npm run test:e2e
```
For more detailed information on the integration testing framework, please see
the [Integration Tests documentation](/docs/integration-tests.md).
the
[Integration Tests documentation](https://geminicli.com/docs/integration-tests).
### Linting and preflight checks
@@ -320,11 +342,9 @@ npm run lint
- Please adhere to the coding style, patterns, and conventions used throughout
the existing codebase.
- Consult
[GEMINI.md](https://github.com/google-gemini/gemini-cli/blob/main/GEMINI.md)
(typically found in the project root) for specific instructions related to
AI-assisted development, including conventions for React, comments, and Git
usage.
- Consult [GEMINI.md](../GEMINI.md) (typically found in the project root) for
specific instructions related to AI-assisted development, including
conventions for React, comments, and Git usage.
- **Imports:** Pay special attention to import paths. The project uses ESLint to
enforce restrictions on relative imports between packages.
@@ -548,7 +568,7 @@ Before submitting your documentation pull request, please:
If you have questions about contributing documentation:
- Check our [FAQ](/docs/resources/faq.md).
- Check our [FAQ](https://geminicli.com/docs/resources/faq).
- Review existing documentation for examples.
- Open [an issue](https://github.com/google-gemini/gemini-cli/issues) to discuss
your proposed changes.
+5 -6
View File
@@ -77,7 +77,7 @@ See [Releases](./docs/releases.md) for more details.
### Preview
New preview releases will be published each week at UTC 2359 on Tuesdays. These
New preview releases will be published each week at UTC 23:59 on Tuesdays. These
releases will not have been fully vetted and may contain regressions or other
outstanding issues. Please help us test and install with `preview` tag.
@@ -87,7 +87,7 @@ npm install -g @google/gemini-cli@preview
### Stable
- New stable releases will be published each week at UTC 2000 on Tuesdays, this
- New stable releases will be published each week at UTC 20:00 on Tuesdays, this
will be the full promotion of last week's `preview` release + any bug fixes
and validations. Use `latest` tag.
@@ -97,7 +97,7 @@ npm install -g @google/gemini-cli@latest
### Nightly
- New releases will be published each day at UTC 0000. This will be all changes
- New releases will be published each day at UTC 00:00. This will be all changes
from the main branch as represented at time of release. It should be assumed
there are pending validations and issues. Use `nightly` tag.
@@ -301,7 +301,7 @@ gemini
### Tools & Extensions
- [**Built-in Tools Overview**](./docs/tools/index.md)
- [**Built-in Tools Overview**](./docs/reference/tools.md)
- [File System Operations](./docs/tools/file-system.md)
- [Shell Commands](./docs/tools/shell.md)
- [Web Fetch & Search](./docs/tools/web-fetch.md)
@@ -323,8 +323,7 @@ gemini
- [**Enterprise Guide**](./docs/cli/enterprise.md) - Deploy and manage in a
corporate environment.
- [**Telemetry & Monitoring**](./docs/cli/telemetry.md) - Usage tracking.
- [**Tools API Development**](./docs/reference/tools-api.md) - Create custom
tools.
- [**Tools reference**](./docs/reference/tools.md) - Built-in tools overview.
- [**Local development**](./docs/local-development.md) - Local development
tooling.
+24
View File
@@ -18,6 +18,30 @@ on GitHub.
| [Preview](preview.md) | Experimental features ready for early feedback. |
| [Stable](latest.md) | Stable, recommended for general use. |
## Announcements: v0.32.0 - 2026-03-03
- **Generalist Agent:** The generalist agent is now enabled to improve task
delegation and routing
([#19665](https://github.com/google-gemini/gemini-cli/pull/19665) by
@joshualitt).
- **Model Steering in Workspace:** Added support for model steering directly in
the workspace
([#20343](https://github.com/google-gemini/gemini-cli/pull/20343) by
@joshualitt).
- **Plan Mode Enhancements:** Users can now open and modify plans in an external
editor, and the planning workflow has been adapted to handle complex tasks
more effectively with multi-select options
([#20348](https://github.com/google-gemini/gemini-cli/pull/20348) by @Adib234,
[#20465](https://github.com/google-gemini/gemini-cli/pull/20465) by @jerop).
- **Interactive Shell Autocompletion:** Introduced interactive shell
autocompletion for a more seamless experience
([#20082](https://github.com/google-gemini/gemini-cli/pull/20082) by
@mrpmohiburrahman).
- **Parallel Extension Loading:** Extensions are now loaded in parallel to
improve startup times
([#20229](https://github.com/google-gemini/gemini-cli/pull/20229) by
@scidomino).
## Announcements: v0.31.0 - 2026-02-27
- **Gemini 3.1 Pro Preview:** Gemini CLI now supports the new Gemini 3.1 Pro
+190 -397
View File
@@ -1,6 +1,6 @@
# Latest stable release: v0.31.0
# Latest stable release: v0.32.1
Released: February 27, 2026
Released: March 4, 2026
For most users, our latest stable release is the recommended release. Install
the latest stable version with:
@@ -11,405 +11,198 @@ npm install -g @google/gemini-cli
## Highlights
- **Gemini 3.1 Pro Preview:** Gemini CLI now supports the new Gemini 3.1 Pro
Preview model.
- **Experimental Browser Agent:** We've introduced a new experimental browser
agent to directly interact with web pages and retrieve context.
- **Policy Engine Updates:** The policy engine has been expanded to support
project-level policies, MCP server wildcards, and tool annotation matching,
providing greater control over tool executions.
- **Web Fetch Enhancements:** A new experimental direct web fetch tool has been
implemented, alongside rate-limiting features for enhanced security.
- **Improved Plan Mode:** Plan Mode now includes support for custom storage
directories, automatic model switching, and summarizing work after execution.
- **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
- Use ranged reads and limited searches and fuzzy editing improvements by
@gundermanc in
[#19240](https://github.com/google-gemini/gemini-cli/pull/19240)
- Fix bottom border color by @jacob314 in
[#19266](https://github.com/google-gemini/gemini-cli/pull/19266)
- Release note generator fix by @g-samroberts in
[#19363](https://github.com/google-gemini/gemini-cli/pull/19363)
- test(evals): add behavioral tests for tool output masking by @NTaylorMullen in
[#19172](https://github.com/google-gemini/gemini-cli/pull/19172)
- docs: clarify preflight instructions in GEMINI.md by @NTaylorMullen in
[#19377](https://github.com/google-gemini/gemini-cli/pull/19377)
- feat(cli): add gemini --resume hint on exit by @Mag1ck in
[#16285](https://github.com/google-gemini/gemini-cli/pull/16285)
- fix: optimize height calculations for ask_user dialog by @jackwotherspoon in
[#19017](https://github.com/google-gemini/gemini-cli/pull/19017)
- feat(cli): add Alt+D for forward word deletion by @scidomino in
[#19300](https://github.com/google-gemini/gemini-cli/pull/19300)
- Disable failing eval test by @chrstnb in
[#19455](https://github.com/google-gemini/gemini-cli/pull/19455)
- fix(cli): support legacy onConfirm callback in ToolActionsContext 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
[#19369](https://github.com/google-gemini/gemini-cli/pull/19369)
- chore(deps): bump tar from 7.5.7 to 7.5.8 by @.github/dependabot.yml[bot] in
[#19367](https://github.com/google-gemini/gemini-cli/pull/19367)
- fix(plan): allow safe fallback when experiment setting for plan is not enabled
but approval mode at startup is plan by @Adib234 in
[#19439](https://github.com/google-gemini/gemini-cli/pull/19439)
- Add explicit color-convert dependency by @chrstnb in
[#19460](https://github.com/google-gemini/gemini-cli/pull/19460)
- feat(devtools): migrate devtools package into monorepo by @SandyTao520 in
[#18936](https://github.com/google-gemini/gemini-cli/pull/18936)
- fix(core): clarify plan mode constraints and exit mechanism by @jerop in
[#19438](https://github.com/google-gemini/gemini-cli/pull/19438)
- feat(cli): add macOS run-event notifications (interactive only) by
@LyalinDotCom in
[#19056](https://github.com/google-gemini/gemini-cli/pull/19056)
- Changelog for v0.29.0 by @gemini-cli-robot in
[#19361](https://github.com/google-gemini/gemini-cli/pull/19361)
- fix(ui): preventing empty history items from being added by @devr0306 in
[#19014](https://github.com/google-gemini/gemini-cli/pull/19014)
- Changelog for v0.30.0-preview.0 by @gemini-cli-robot in
[#19364](https://github.com/google-gemini/gemini-cli/pull/19364)
- feat(core): add support for MCP progress updates by @NTaylorMullen in
[#19046](https://github.com/google-gemini/gemini-cli/pull/19046)
- fix(core): ensure directory exists before writing conversation file by
@godwiniheuwa in
[#18429](https://github.com/google-gemini/gemini-cli/pull/18429)
- fix(ui): move margin from top to bottom in ToolGroupMessage by @imadraude in
[#17198](https://github.com/google-gemini/gemini-cli/pull/17198)
- fix(cli): treat unknown slash commands as regular input instead of showing
error by @skyvanguard in
[#17393](https://github.com/google-gemini/gemini-cli/pull/17393)
- feat(core): experimental in-progress steering hints (2 of 2) by @joshualitt in
[#19307](https://github.com/google-gemini/gemini-cli/pull/19307)
- docs(plan): add documentation for plan mode command by @Adib234 in
[#19467](https://github.com/google-gemini/gemini-cli/pull/19467)
- fix(core): ripgrep fails when pattern looks like ripgrep flag by @syvb in
[#18858](https://github.com/google-gemini/gemini-cli/pull/18858)
- fix(cli): disable auto-completion on Shift+Tab to preserve mode cycling by
@NTaylorMullen in
[#19451](https://github.com/google-gemini/gemini-cli/pull/19451)
- use issuer instead of authorization_endpoint for oauth discovery by
@garrettsparks in
[#17332](https://github.com/google-gemini/gemini-cli/pull/17332)
- feat(cli): include `/dir add` directories in @ autocomplete suggestions by
@jasmeetsb in [#19246](https://github.com/google-gemini/gemini-cli/pull/19246)
- feat(admin): Admin settings should only apply if adminControlsApplicable =
true and fetch errors should be fatal by @skeshive in
[#19453](https://github.com/google-gemini/gemini-cli/pull/19453)
- Format strict-development-rules command by @g-samroberts in
[#19484](https://github.com/google-gemini/gemini-cli/pull/19484)
- feat(core): centralize compatibility checks and add TrueColor detection by
[#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
[#19478](https://github.com/google-gemini/gemini-cli/pull/19478)
- Remove unused files and update index and sidebar. by @g-samroberts in
[#19479](https://github.com/google-gemini/gemini-cli/pull/19479)
- Migrate core render util to use xterm.js as part of the rendering loop. by
@jacob314 in [#19044](https://github.com/google-gemini/gemini-cli/pull/19044)
- Changelog for v0.30.0-preview.1 by @gemini-cli-robot in
[#19496](https://github.com/google-gemini/gemini-cli/pull/19496)
- build: replace deprecated built-in punycode with userland package by @jacob314
in [#19502](https://github.com/google-gemini/gemini-cli/pull/19502)
- Speculative fixes to try to fix react error. by @jacob314 in
[#19508](https://github.com/google-gemini/gemini-cli/pull/19508)
- fix spacing by @jacob314 in
[#19494](https://github.com/google-gemini/gemini-cli/pull/19494)
- fix(core): ensure user rejections update tool outcome for telemetry by
@abhiasap in [#18982](https://github.com/google-gemini/gemini-cli/pull/18982)
- fix(acp): Initialize config (#18897) by @Mervap in
[#18898](https://github.com/google-gemini/gemini-cli/pull/18898)
- fix(core): add error logging for IDE fetch failures by @yuvrajangadsingh in
[#17981](https://github.com/google-gemini/gemini-cli/pull/17981)
- feat(acp): support set_mode interface (#18890) by @Mervap in
[#18891](https://github.com/google-gemini/gemini-cli/pull/18891)
- fix(core): robust workspace-based IDE connection discovery by @ehedlund in
[#18443](https://github.com/google-gemini/gemini-cli/pull/18443)
- Deflake windows tests. by @jacob314 in
[#19511](https://github.com/google-gemini/gemini-cli/pull/19511)
- Fix: Avoid tool confirmation timeout when no UI listeners are present by
@pdHaku0 in [#17955](https://github.com/google-gemini/gemini-cli/pull/17955)
- format md file by @scidomino in
[#19474](https://github.com/google-gemini/gemini-cli/pull/19474)
- feat(cli): add experimental.useOSC52Copy setting by @scidomino in
[#19488](https://github.com/google-gemini/gemini-cli/pull/19488)
- feat(cli): replace loading phrases boolean with enum setting by @LyalinDotCom
in [#19347](https://github.com/google-gemini/gemini-cli/pull/19347)
- Update skill to adjust for generated results. by @g-samroberts in
[#19500](https://github.com/google-gemini/gemini-cli/pull/19500)
- Fix message too large issue. by @gundermanc in
[#19499](https://github.com/google-gemini/gemini-cli/pull/19499)
- fix(core): prevent duplicate tool approval entries in auto-saved.toml by
@Abhijit-2592 in
[#19487](https://github.com/google-gemini/gemini-cli/pull/19487)
- fix(core): resolve crash in ClearcutLogger when os.cpus() is empty by @Adib234
in [#19555](https://github.com/google-gemini/gemini-cli/pull/19555)
- chore(core): improve encapsulation and remove unused exports by @adamfweidman
in [#19556](https://github.com/google-gemini/gemini-cli/pull/19556)
- Revert "Add generic searchable list to back settings and extensions (… by
@chrstnb in [#19434](https://github.com/google-gemini/gemini-cli/pull/19434)
- fix(core): improve error type extraction for telemetry by @yunaseoul in
[#19565](https://github.com/google-gemini/gemini-cli/pull/19565)
- fix: remove extra padding in Composer by @jackwotherspoon in
[#19529](https://github.com/google-gemini/gemini-cli/pull/19529)
- feat(plan): support configuring custom plans storage directory by @jerop in
[#19577](https://github.com/google-gemini/gemini-cli/pull/19577)
- Migrate files to resource or references folder. by @g-samroberts in
[#19503](https://github.com/google-gemini/gemini-cli/pull/19503)
- feat(policy): implement project-level policy support by @Abhijit-2592 in
[#18682](https://github.com/google-gemini/gemini-cli/pull/18682)
- feat(core): Implement parallel FC for read only tools. by @joshualitt in
[#18791](https://github.com/google-gemini/gemini-cli/pull/18791)
- chore(skills): adds pr-address-comments skill to work on PR feedback by
@mbleigh in [#19576](https://github.com/google-gemini/gemini-cli/pull/19576)
- refactor(sdk): introduce session-based architecture by @mbleigh in
[#19180](https://github.com/google-gemini/gemini-cli/pull/19180)
- fix(ci): add fallback JSON extraction to issue triage workflow by @bdmorgan in
[#19593](https://github.com/google-gemini/gemini-cli/pull/19593)
- feat(core): refine Edit and WriteFile tool schemas for Gemini 3 by
@SandyTao520 in
[#19476](https://github.com/google-gemini/gemini-cli/pull/19476)
- Changelog for v0.30.0-preview.3 by @gemini-cli-robot in
[#19585](https://github.com/google-gemini/gemini-cli/pull/19585)
- fix(plan): exclude EnterPlanMode tool from YOLO mode by @Adib234 in
[#19570](https://github.com/google-gemini/gemini-cli/pull/19570)
- chore: resolve build warnings and update dependencies by @mattKorwel in
[#18880](https://github.com/google-gemini/gemini-cli/pull/18880)
- feat(ui): add source indicators to slash commands by @ehedlund in
[#18839](https://github.com/google-gemini/gemini-cli/pull/18839)
- docs: refine Plan Mode documentation structure and workflow by @jerop in
[#19644](https://github.com/google-gemini/gemini-cli/pull/19644)
- Docs: Update release information regarding Gemini 3.1 by @jkcinouye in
[#19568](https://github.com/google-gemini/gemini-cli/pull/19568)
- fix(security): rate limit web_fetch tool to mitigate DDoS via prompt injection
by @mattKorwel in
[#19567](https://github.com/google-gemini/gemini-cli/pull/19567)
- Add initial implementation of /extensions explore command by @chrstnb in
[#19029](https://github.com/google-gemini/gemini-cli/pull/19029)
- fix: use discoverOAuthFromWWWAuthenticate for reactive OAuth flow (#18760) by
@maximus12793 in
[#19038](https://github.com/google-gemini/gemini-cli/pull/19038)
- Search updates by @alisa-alisa in
[#19482](https://github.com/google-gemini/gemini-cli/pull/19482)
- feat(cli): add support for numpad SS3 sequences by @scidomino in
[#19659](https://github.com/google-gemini/gemini-cli/pull/19659)
- feat(cli): enhance folder trust with configuration discovery and security
warnings by @galz10 in
[#19492](https://github.com/google-gemini/gemini-cli/pull/19492)
- feat(ui): improve startup warnings UX with dismissal and show-count limits by
@spencer426 in
[#19584](https://github.com/google-gemini/gemini-cli/pull/19584)
- feat(a2a): Add API key authentication provider by @adamfweidman in
[#19548](https://github.com/google-gemini/gemini-cli/pull/19548)
- Send accepted/removed lines with ACCEPT_FILE telemetry. by @gundermanc in
[#19670](https://github.com/google-gemini/gemini-cli/pull/19670)
- feat(models): support Gemini 3.1 Pro Preview and fixes by @sehoon38 in
[#19676](https://github.com/google-gemini/gemini-cli/pull/19676)
- feat(plan): enforce read-only constraints in Plan Mode by @mattKorwel in
[#19433](https://github.com/google-gemini/gemini-cli/pull/19433)
- fix(cli): allow perfect match @scripts/test-windows-paths.js completions to
submit on Enter by @spencer426 in
[#19562](https://github.com/google-gemini/gemini-cli/pull/19562)
- fix(core): treat 503 Service Unavailable as retryable quota error by @sehoon38
in [#19642](https://github.com/google-gemini/gemini-cli/pull/19642)
- Update sidebar.json for to allow top nav tabs. by @g-samroberts in
[#19595](https://github.com/google-gemini/gemini-cli/pull/19595)
- security: strip deceptive Unicode characters from terminal output by @ehedlund
in [#19026](https://github.com/google-gemini/gemini-cli/pull/19026)
- Fixes 'input.on' is not a function error in Gemini CLI by @gundermanc in
[#19691](https://github.com/google-gemini/gemini-cli/pull/19691)
- Revert "feat(ui): add source indicators to slash commands" by @ehedlund in
[#19695](https://github.com/google-gemini/gemini-cli/pull/19695)
- security: implement deceptive URL detection and disclosure in tool
confirmations by @ehedlund in
[#19288](https://github.com/google-gemini/gemini-cli/pull/19288)
- fix(core): restore auth consent in headless mode and add unit tests by
@ehedlund in [#19689](https://github.com/google-gemini/gemini-cli/pull/19689)
- Fix unsafe assertions in code_assist folder. by @gundermanc in
[#19706](https://github.com/google-gemini/gemini-cli/pull/19706)
- feat(cli): make JetBrains warning more specific by @jacob314 in
[#19687](https://github.com/google-gemini/gemini-cli/pull/19687)
- fix(cli): extensions dialog UX polish by @jacob314 in
[#19685](https://github.com/google-gemini/gemini-cli/pull/19685)
- fix(cli): use getDisplayString for manual model selection in dialog by
@sehoon38 in [#19726](https://github.com/google-gemini/gemini-cli/pull/19726)
- feat(policy): repurpose "Always Allow" persistence to workspace level by
@Abhijit-2592 in
[#19707](https://github.com/google-gemini/gemini-cli/pull/19707)
- fix(cli): re-enable CLI banner by @sehoon38 in
[#19741](https://github.com/google-gemini/gemini-cli/pull/19741)
- Disallow and suppress unsafe assignment by @gundermanc in
[#19736](https://github.com/google-gemini/gemini-cli/pull/19736)
- feat(core): migrate read_file to 1-based start_line/end_line parameters by
@adamfweidman in
[#19526](https://github.com/google-gemini/gemini-cli/pull/19526)
- feat(cli): improve CTRL+O experience for both standard and alternate screen
buffer (ASB) modes by @jwhelangoog in
[#19010](https://github.com/google-gemini/gemini-cli/pull/19010)
- Utilize pipelining of grep_search -> read_file to eliminate turns by
@gundermanc in
[#19574](https://github.com/google-gemini/gemini-cli/pull/19574)
- refactor(core): remove unsafe type assertions in error utils (Phase 1.1) by
@mattKorwel in
[#19750](https://github.com/google-gemini/gemini-cli/pull/19750)
- Disallow unsafe returns. by @gundermanc in
[#19767](https://github.com/google-gemini/gemini-cli/pull/19767)
- fix(cli): filter subagent sessions from resume history by @abhipatel12 in
[#19698](https://github.com/google-gemini/gemini-cli/pull/19698)
- chore(lint): fix lint errors seen when running npm run lint by @abhipatel12 in
[#19844](https://github.com/google-gemini/gemini-cli/pull/19844)
- feat(core): remove unnecessary login verbiage from Code Assist auth by
@NTaylorMullen in
[#19861](https://github.com/google-gemini/gemini-cli/pull/19861)
- fix(plan): time share by approval mode dashboard reporting negative time
shares by @Adib234 in
[#19847](https://github.com/google-gemini/gemini-cli/pull/19847)
- fix(core): allow any preview model in quota access check by @bdmorgan in
[#19867](https://github.com/google-gemini/gemini-cli/pull/19867)
- fix(core): prevent omission placeholder deletions in replace/write_file by
@nsalerni in [#19870](https://github.com/google-gemini/gemini-cli/pull/19870)
- fix(core): add uniqueness guard to edit tool by @Shivangisharma4 in
[#19890](https://github.com/google-gemini/gemini-cli/pull/19890)
- refactor(config): remove enablePromptCompletion from settings by @sehoon38 in
[#19974](https://github.com/google-gemini/gemini-cli/pull/19974)
- refactor(core): move session conversion logic to core by @abhipatel12 in
[#19972](https://github.com/google-gemini/gemini-cli/pull/19972)
- Fix: Persist manual model selection on restart #19864 by @Nixxx19 in
[#19891](https://github.com/google-gemini/gemini-cli/pull/19891)
- fix(core): increase default retry attempts and add quota error backoff by
@sehoon38 in [#19949](https://github.com/google-gemini/gemini-cli/pull/19949)
- feat(core): add policy chain support for Gemini 3.1 by @sehoon38 in
[#19991](https://github.com/google-gemini/gemini-cli/pull/19991)
- Updates command reference and /stats command. by @g-samroberts in
[#19794](https://github.com/google-gemini/gemini-cli/pull/19794)
- Fix for silent failures in non-interactive mode by @owenofbrien in
[#19905](https://github.com/google-gemini/gemini-cli/pull/19905)
- fix(plan): allow plan mode writes on Windows and fix prompt paths by @Adib234
in [#19658](https://github.com/google-gemini/gemini-cli/pull/19658)
- fix(core): prevent OAuth server crash on unexpected requests by @reyyanxahmed
in [#19668](https://github.com/google-gemini/gemini-cli/pull/19668)
- feat: Map tool kinds to explicit ACP.ToolKind values and update test … by
@sripasg in [#19547](https://github.com/google-gemini/gemini-cli/pull/19547)
- chore: restrict gemini-automted-issue-triage to only allow echo by @galz10 in
[#20047](https://github.com/google-gemini/gemini-cli/pull/20047)
- Allow ask headers longer than 16 chars by @scidomino in
[#20041](https://github.com/google-gemini/gemini-cli/pull/20041)
- fix(core): prevent state corruption in McpClientManager during collis by @h30s
in [#19782](https://github.com/google-gemini/gemini-cli/pull/19782)
- fix(bundling): copy devtools package to bundle for runtime resolution by
@SandyTao520 in
[#19766](https://github.com/google-gemini/gemini-cli/pull/19766)
- feat(policy): Support MCP Server Wildcards in Policy Engine by @jerop in
[#20024](https://github.com/google-gemini/gemini-cli/pull/20024)
- docs(CONTRIBUTING): update React DevTools version to 6 by @mmgok in
[#20014](https://github.com/google-gemini/gemini-cli/pull/20014)
- feat(core): optimize tool descriptions and schemas for Gemini 3 by
@aishaneeshah in
[#19643](https://github.com/google-gemini/gemini-cli/pull/19643)
- feat(core): implement experimental direct web fetch by @mbleigh in
[#19557](https://github.com/google-gemini/gemini-cli/pull/19557)
- feat(core): replace expected_replacements with allow_multiple in replace tool
by @SandyTao520 in
[#20033](https://github.com/google-gemini/gemini-cli/pull/20033)
- fix(sandbox): harden image packaging integrity checks by @aviralgarg05 in
[#19552](https://github.com/google-gemini/gemini-cli/pull/19552)
- fix(core): allow environment variable expansion and explicit overrides for MCP
servers by @galz10 in
[#18837](https://github.com/google-gemini/gemini-cli/pull/18837)
- feat(policy): Implement Tool Annotation Matching in Policy Engine by @jerop in
[#20029](https://github.com/google-gemini/gemini-cli/pull/20029)
- fix(core): prevent utility calls from changing session active model by
@adamfweidman in
[#20035](https://github.com/google-gemini/gemini-cli/pull/20035)
- fix(cli): skip workspace policy loading when in home directory by
@Abhijit-2592 in
[#20054](https://github.com/google-gemini/gemini-cli/pull/20054)
- fix(scripts): Add Windows (win32/x64) support to lint.js by @ZafeerMahmood in
[#16193](https://github.com/google-gemini/gemini-cli/pull/16193)
- fix(a2a-server): Remove unsafe type assertions in agent by @Nixxx19 in
[#19723](https://github.com/google-gemini/gemini-cli/pull/19723)
- Fix: Handle corrupted token file gracefully when switching auth types (#19845)
by @Nixxx19 in
[#19850](https://github.com/google-gemini/gemini-cli/pull/19850)
- fix critical dep vulnerability by @scidomino in
[#20087](https://github.com/google-gemini/gemini-cli/pull/20087)
- Add new setting to configure maxRetries by @kevinjwang1 in
[#20064](https://github.com/google-gemini/gemini-cli/pull/20064)
- Stabilize tests. by @gundermanc in
[#20095](https://github.com/google-gemini/gemini-cli/pull/20095)
- make windows tests mandatory by @scidomino in
[#20096](https://github.com/google-gemini/gemini-cli/pull/20096)
- Add 3.1 pro preview to behavioral evals. by @gundermanc in
[#20088](https://github.com/google-gemini/gemini-cli/pull/20088)
- feat:PR-rate-limit by @JagjeevanAK in
[#19804](https://github.com/google-gemini/gemini-cli/pull/19804)
- feat(cli): allow expanding full details of MCP tool on approval by @y-okt in
[#19916](https://github.com/google-gemini/gemini-cli/pull/19916)
- feat(security): Introduce Conseca framework by @shrishabh in
[#13193](https://github.com/google-gemini/gemini-cli/pull/13193)
- fix(cli): Remove unsafe type assertions in activityLogger #19713 by @Nixxx19
in [#19745](https://github.com/google-gemini/gemini-cli/pull/19745)
- feat: implement AfterTool tail tool calls by @googlestrobe in
[#18486](https://github.com/google-gemini/gemini-cli/pull/18486)
- ci(actions): fix PR rate limiter excluding maintainers by @scidomino in
[#20117](https://github.com/google-gemini/gemini-cli/pull/20117)
- Shortcuts: Move SectionHeader title below top line and refine styling by
@keithguerin in
[#18721](https://github.com/google-gemini/gemini-cli/pull/18721)
- refactor(ui): Update and simplify use of gray colors in themes by @keithguerin
in [#20141](https://github.com/google-gemini/gemini-cli/pull/20141)
- fix punycode2 by @jacob314 in
[#20154](https://github.com/google-gemini/gemini-cli/pull/20154)
- feat(ide): add GEMINI_CLI_IDE_PID env var to override IDE process detection by
@kiryltech in [#15842](https://github.com/google-gemini/gemini-cli/pull/15842)
- feat(policy): Propagate Tool Annotations for MCP Servers by @jerop in
[#20083](https://github.com/google-gemini/gemini-cli/pull/20083)
- fix(a2a-server): pass allowedTools settings to core Config by @reyyanxahmed in
[#19680](https://github.com/google-gemini/gemini-cli/pull/19680)
- feat(mcp): add progress bar, throttling, and input validation for MCP tool
progress by @jasmeetsb in
[#19772](https://github.com/google-gemini/gemini-cli/pull/19772)
- feat(policy): centralize plan mode tool visibility in policy engine by @jerop
in [#20178](https://github.com/google-gemini/gemini-cli/pull/20178)
- feat(browser): implement experimental browser agent by @gsquared94 in
[#19284](https://github.com/google-gemini/gemini-cli/pull/19284)
- feat(plan): summarize work after executing a plan by @jerop in
[#19432](https://github.com/google-gemini/gemini-cli/pull/19432)
- fix(core): create new McpClient on restart to apply updated config by @h30s in
[#20126](https://github.com/google-gemini/gemini-cli/pull/20126)
- Changelog for v0.30.0-preview.5 by @gemini-cli-robot in
[#20107](https://github.com/google-gemini/gemini-cli/pull/20107)
- Update packages. by @jacob314 in
[#20152](https://github.com/google-gemini/gemini-cli/pull/20152)
- Fix extension env dir loading issue by @chrstnb in
[#20198](https://github.com/google-gemini/gemini-cli/pull/20198)
- restrict /assign to help-wanted issues by @scidomino in
[#20207](https://github.com/google-gemini/gemini-cli/pull/20207)
- feat(plan): inject message when user manually exits Plan mode by @jerop in
[#20203](https://github.com/google-gemini/gemini-cli/pull/20203)
- feat(extensions): enforce folder trust for local extension install by @galz10
in [#19703](https://github.com/google-gemini/gemini-cli/pull/19703)
- feat(hooks): adds support for RuntimeHook functions. by @mbleigh in
[#19598](https://github.com/google-gemini/gemini-cli/pull/19598)
- Docs: Update UI links. by @jkcinouye in
[#20224](https://github.com/google-gemini/gemini-cli/pull/20224)
- feat: prompt users to run /terminal-setup with yes/no by @ishaanxgupta in
[#16235](https://github.com/google-gemini/gemini-cli/pull/16235)
- fix: additional high vulnerabilities (minimatch, cross-spawn) by @adamfweidman
in [#20221](https://github.com/google-gemini/gemini-cli/pull/20221)
- feat(telemetry): Add context breakdown to API response event by @SandyTao520
in [#19699](https://github.com/google-gemini/gemini-cli/pull/19699)
- Docs: Add nested sub-folders for related topics by @g-samroberts in
[#20235](https://github.com/google-gemini/gemini-cli/pull/20235)
- feat(plan): support automatic model switching for Plan Mode by @jerop in
[#20240](https://github.com/google-gemini/gemini-cli/pull/20240)
- fix(patch): cherry-pick 58df1c6 to release/v0.31.0-preview.0-pr-20374 to patch
version v0.31.0-preview.0 and create version 0.31.0-preview.1 by
@gemini-cli-robot in
[#20568](https://github.com/google-gemini/gemini-cli/pull/20568)
- fix(patch): cherry-pick ea48bd9 to release/v0.31.0-preview.1-pr-20577
[CONFLICTS] by @gemini-cli-robot in
[#20592](https://github.com/google-gemini/gemini-cli/pull/20592)
- fix(patch): cherry-pick 32e777f to release/v0.31.0-preview.2-pr-20531 to patch
version v0.31.0-preview.2 and create version 0.31.0-preview.3 by
@gemini-cli-robot in
[#20607](https://github.com/google-gemini/gemini-cli/pull/20607)
[#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.30.1...v0.31.0
https://github.com/google-gemini/gemini-cli/compare/v0.31.0...v0.32.1
+182 -189
View File
@@ -1,6 +1,6 @@
# Preview release: v0.32.0-preview.0
# Preview release: v0.33.0-preview.4
Released: February 27, 2026
Released: March 06, 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,196 +13,189 @@ npm install -g @google/gemini-cli@preview
## Highlights
- **Plan Mode Enhancements**: Significant updates to Plan Mode, including
support for modifying plans in external editors, adaptive workflows based on
task complexity, and new integration tests.
- **Agent and Core Engine Updates**: Enabled the generalist agent, introduced
`Kind.Agent` for sub-agent classification, implemented task tracking
foundation, and improved Agent-to-Agent (A2A) streaming and content
extraction.
- **CLI & User Experience**: Introduced interactive shell autocompletion, added
a new verbosity mode for cleaner error reporting, enabled parallel loading of
extensions, and improved UI hints and shortcut handling.
- **Billing and Security**: Implemented G1 AI credits overage flow with enhanced
billing telemetry, updated the authentication handshake to specification, and
added support for a policy engine in extensions.
- **Stability and Bug Fixes**: Addressed numerous issues including 100% CPU
consumption by orphaned processes, enhanced retry logic for Code Assist,
reduced intrusive MCP errors, and merged duplicate imports across packages.
- **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
- 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
- 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
[#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
[#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
[#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
[#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)
[#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)
**Full Changelog**:
https://github.com/google-gemini/gemini-cli/compare/v0.31.0-preview.3...v0.32.0-preview.0
https://github.com/google-gemini/gemini-cli/compare/v0.32.0-preview.0...v0.33.0-preview.4
+21 -5
View File
@@ -8,7 +8,8 @@ and parameters.
| Command | Description | Example |
| ---------------------------------- | ---------------------------------- | ------------------------------------------------------------ |
| `gemini` | Start interactive REPL | `gemini` |
| `gemini "query"` | Query non-interactively, then exit | `gemini "explain this project"` |
| `gemini -p "query"` | Query non-interactively | `gemini -p "summarize README.md"` |
| `gemini "query"` | Query and continue interactively | `gemini "explain this project"` |
| `cat file \| gemini` | Process piped content | `cat logs.txt \| gemini`<br>`Get-Content logs.txt \| gemini` |
| `gemini -i "query"` | Execute and continue interactively | `gemini -i "What is the purpose of this project?"` |
| `gemini -r "latest"` | Continue most recent session | `gemini -r "latest"` |
@@ -20,9 +21,24 @@ and parameters.
### Positional arguments
| Argument | Type | Description |
| -------- | ----------------- | ------------------------------------------------------------------------------------------------------------------ |
| `query` | string (variadic) | Positional prompt. Defaults to one-shot mode. Use `-i/--prompt-interactive` to execute and continue interactively. |
| Argument | Type | Description |
| -------- | ----------------- | ---------------------------------------------------------------------------------------------------------- |
| `query` | string (variadic) | Positional prompt. Defaults to interactive mode in a TTY. Use `-p/--prompt` for non-interactive execution. |
## Interactive commands
These commands are available within the interactive REPL.
| Command | Description |
| -------------------- | ---------------------------------------- |
| `/skills reload` | Reload discovered skills from disk |
| `/agents reload` | Reload the agent registry |
| `/commands reload` | Reload custom slash commands |
| `/memory reload` | Reload context files (e.g., `GEMINI.md`) |
| `/mcp reload` | Restart and reload MCP servers |
| `/extensions reload` | Reload all active extensions |
| `/help` | Show help for all commands |
| `/quit` | Exit the interactive session |
## CLI Options
@@ -32,7 +48,7 @@ and parameters.
| `--version` | `-v` | - | - | Show CLI version number and exit |
| `--help` | `-h` | - | - | Show help information |
| `--model` | `-m` | string | `auto` | Model to use. See [Model Selection](#model-selection) for available values. |
| `--prompt` | `-p` | string | - | Prompt text. Appended to stdin input if provided. **Deprecated:** Use positional arguments instead. |
| `--prompt` | `-p` | string | - | Prompt text. Appended to stdin input if provided. Forces non-interactive mode. |
| `--prompt-interactive` | `-i` | string | - | Execute prompt and continue in interactive mode |
| `--sandbox` | `-s` | boolean | `false` | Run in a sandboxed environment for safer execution |
| `--approval-mode` | - | string | `default` | Approval mode for tool execution. Choices: `default`, `auto_edit`, `yolo` |
+3 -3
View File
@@ -244,7 +244,7 @@ gemini
You can significantly enhance security by controlling which tools the Gemini
model can use. This is achieved through the `tools.core` setting and the
[Policy Engine](../reference/policy-engine.md). For a list of available tools,
see the [Tools documentation](../tools/index.md).
see the [Tools reference](../reference/tools.md).
### Allowlisting with `coreTools`
@@ -308,8 +308,8 @@ unintended tool execution.
## Managing custom tools (MCP servers)
If your organization uses custom tools via
[Model-Context Protocol (MCP) servers](../reference/tools-api.md), it is crucial
to understand how server configurations are managed to apply security policies
[Model-Context Protocol (MCP) servers](../tools/mcp-server.md), it is crucial to
understand how server configurations are managed to apply security policies
effectively.
### How MCP server configurations are merged
+1 -1
View File
@@ -63,7 +63,7 @@ You can interact with the loaded context files by using the `/memory` command.
- **`/memory show`**: Displays the full, concatenated content of the current
hierarchical memory. This lets you inspect the exact instructional context
being provided to the model.
- **`/memory refresh`**: Forces a re-scan and reload of all `GEMINI.md` files
- **`/memory reload`**: Forces a re-scan and reload of all `GEMINI.md` files
from all configured locations.
- **`/memory add <text>`**: Appends your text to your global
`~/.gemini/GEMINI.md` file. This lets you add persistent memories on the fly.
+1 -1
View File
@@ -6,7 +6,7 @@ structured text or JSON output without an interactive terminal UI.
## Technical reference
Headless mode is triggered when the CLI is run in a non-TTY environment or when
providing a query as a positional argument without the interactive flag.
providing a query with the `-p` (or `--prompt`) flag.
### Output formats
+58
View File
@@ -0,0 +1,58 @@
# Notifications (experimental)
Gemini CLI can send system notifications to alert you when a session completes
or when it needs your attention, such as when it's waiting for you to approve a
tool call.
> **Note:** This is a preview feature currently under active development.
> Preview features may be available on the **Preview** channel or may need to be
> enabled under `/settings`.
Notifications are particularly useful when running long-running tasks or using
[Plan Mode](./plan-mode.md), letting you switch to other windows while Gemini
CLI works in the background.
## Requirements
Currently, system notifications are only supported on macOS.
### Terminal support
The CLI uses the OSC 9 terminal escape sequence to trigger system notifications.
This is supported by several modern terminal emulators. If your terminal does
not support OSC 9 notifications, Gemini CLI falls back to a system alert sound
to get your attention.
## Enable notifications
Notifications are disabled by default. You can enable them using the `/settings`
command or by updating your `settings.json` file.
1. Open the settings dialog by typing `/settings` in an interactive session.
2. Navigate to the **General** category.
3. Toggle the **Enable Notifications** setting to **On**.
Alternatively, add the following to your `settings.json`:
```json
{
"general": {
"enableNotifications": true
}
}
```
## Types of notifications
Gemini CLI sends notifications for the following events:
- **Action required:** Triggered when the model is waiting for user input or
tool approval. This helps you know when the CLI has paused and needs you to
intervene.
- **Session complete:** Triggered when a session finishes successfully. This is
useful for tracking the completion of automated tasks.
## Next steps
- Start planning with [Plan Mode](./plan-mode.md).
- Configure your experience with other [settings](./settings.md).
+172 -151
View File
@@ -1,73 +1,40 @@
# Plan Mode (experimental)
# Plan Mode
Plan Mode is a read-only environment for architecting robust solutions before
implementation. It allows you to:
implementation. With Plan Mode, you can:
- **Research:** Explore the project in a read-only state to prevent accidental
changes.
- **Design:** Understand problems, evaluate trade-offs, and choose a solution.
- **Plan:** Align on an execution strategy before any code is modified.
> **Note:** This is a preview feature currently under active development. Your
> feedback is invaluable as we refine this feature. If you have ideas,
> suggestions, or encounter issues:
>
> - [Open an issue](https://github.com/google-gemini/gemini-cli/issues) on
> GitHub.
> - Use the **/bug** command within Gemini CLI to file an issue.
Plan Mode is enabled by default. You can manage this setting using the
`/settings` command.
- [Enabling Plan Mode](#enabling-plan-mode)
- [How to use Plan Mode](#how-to-use-plan-mode)
- [Entering Plan Mode](#entering-plan-mode)
- [Planning Workflow](#planning-workflow)
- [Exiting Plan Mode](#exiting-plan-mode)
- [Tool Restrictions](#tool-restrictions)
- [Customizing Planning with Skills](#customizing-planning-with-skills)
- [Customizing Policies](#customizing-policies)
- [Example: Allow git commands in Plan Mode](#example-allow-git-commands-in-plan-mode)
- [Example: Enable custom subagents in Plan Mode](#example-enable-custom-subagents-in-plan-mode)
- [Custom Plan Directory and Policies](#custom-plan-directory-and-policies)
- [Automatic Model Routing](#automatic-model-routing)
- [Cleanup](#cleanup)
## How to enter Plan Mode
## Enabling Plan Mode
Plan Mode integrates seamlessly into your workflow, letting you switch between
planning and execution as needed.
To use Plan Mode, enable it via **/settings** (search for **Plan**) or add the
following to your `settings.json`:
You can either configure Gemini CLI to start in Plan Mode by default or enter
Plan Mode manually during a session.
```json
{
"experimental": {
"plan": true
}
}
```
### Launch in Plan Mode
## How to use Plan Mode
To start Gemini CLI directly in Plan Mode by default:
### Entering Plan Mode
1. Use the `/settings` command.
2. Set **Default Approval Mode** to `Plan`.
You can configure Gemini CLI to start in Plan Mode by default or enter it
manually during a session.
To launch Gemini CLI in Plan Mode once:
- **Configuration:** Configure Gemini CLI to start directly in Plan Mode by
default:
1. Type `/settings` in the CLI.
2. Search for **Default Approval Mode**.
3. Set the value to **Plan**.
1. Use `gemini --approval-mode=plan` when launching Gemini CLI.
Alternatively, use the `gemini --approval-mode=plan` CLI flag or manually
update:
### Enter Plan Mode manually
```json
{
"general": {
"defaultApprovalMode": "plan"
}
}
```
To start Plan Mode while using Gemini CLI:
- **Keyboard Shortcut:** Press `Shift+Tab` to cycle through approval modes
- **Keyboard shortcut:** Press `Shift+Tab` to cycle through approval modes
(`Default` -> `Auto-Edit` -> `Plan`).
> **Note:** Plan Mode is automatically removed from the rotation when Gemini
@@ -75,56 +42,58 @@ manually during a session.
- **Command:** Type `/plan` in the input box.
- **Natural Language:** Ask Gemini CLI to "start a plan for...". Gemini CLI then
calls the [`enter_plan_mode`] tool to switch modes.
> **Note:** This tool is not available when Gemini CLI is in [YOLO mode].
- **Natural Language:** Ask Gemini CLI to "start a plan for...". Gemini CLI
calls the
[`enter_plan_mode`](../tools/planning.md#1-enter_plan_mode-enterplanmode) tool
to switch modes.
> **Note:** This tool is not available when Gemini CLI is in
> [YOLO mode](../reference/configuration.md#command-line-arguments).
### Planning Workflow
## How to use Plan Mode
Plan Mode uses an adaptive planning workflow where the research depth, plan
structure, and consultation level are proportional to the task's complexity:
Plan Mode lets you collaborate with Gemini CLI to design a solution before
Gemini CLI takes action.
1. **Explore & Analyze:** Analyze requirements and use read-only tools to map
affected modules and identify dependencies.
2. **Consult:** The depth of consultation is proportional to the task's
complexity:
- **Simple Tasks:** Proceed directly to drafting.
- **Standard Tasks:** Present a summary of viable approaches via
[`ask_user`] for selection.
- **Complex Tasks:** Present detailed trade-offs for at least two viable
approaches via [`ask_user`] and obtain approval before drafting.
3. **Draft:** Write a detailed implementation plan to the
[plans directory](#custom-plan-directory-and-policies). The plan's structure
adapts to the task:
- **Simple Tasks:** Focused on specific **Changes** and **Verification**
steps.
- **Standard Tasks:** Includes an **Objective**, **Key Files & Context**,
**Implementation Steps**, and **Verification & Testing**.
- **Complex Tasks:** Comprehensive plans including **Background &
Motivation**, **Scope & Impact**, **Proposed Solution**, **Alternatives
Considered**, a phased **Implementation Plan**, **Verification**, and
**Migration & Rollback** strategies.
4. **Review & Approval:** Use the [`exit_plan_mode`] tool to present the plan
and formally request approval.
- **Approve:** Exit Plan Mode and start implementation.
- **Iterate:** Provide feedback to refine the plan.
- **Refine manually:** Press **Ctrl + X** to open the plan file in your
[preferred external editor]. This allows you to manually refine the plan
steps before approval. If you make any changes and save the file, the CLI
will automatically send the updated plan back to the agent for review and
iteration.
1. **Provide a goal:** Start by describing what you want to achieve. Gemini CLI
will then enter Plan Mode (if it's not already) to research the task.
2. **Review research and provide input:** As Gemini CLI analyzes your codebase,
it may ask you questions or present different implementation options using
[`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. 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. 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](#customizing-planning-with-skills).
[customize the planning workflow with skills](#custom-planning-with-skills).
### Exiting Plan Mode
## How to exit Plan Mode
To exit Plan Mode, you can:
You can exit Plan Mode at any time, whether you have finalized a plan or want to
switch back to another mode.
- **Keyboard Shortcut:** Press `Shift+Tab` to cycle to the desired mode.
- **Approve a plan:** When Gemini CLI presents a finalized plan, approving it
automatically exits Plan Mode and starts the implementation.
- **Keyboard shortcut:** Press `Shift+Tab` to cycle to the desired mode.
- **Natural language:** Ask Gemini CLI to "exit plan mode" or "stop planning."
- **Tool:** Gemini CLI calls the [`exit_plan_mode`] tool to present the
finalized plan for your approval.
## 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
@@ -132,25 +101,33 @@ Plan Mode enforces strict safety policies to prevent accidental changes.
These are the only allowed tools:
- **FileSystem (Read):** [`read_file`], [`list_directory`], [`glob`]
- **Search:** [`grep_search`], [`google_web_search`]
- **Research Subagents:** [`codebase_investigator`], [`cli_help`]
- **Interaction:** [`ask_user`]
- **MCP Tools (Read):** Read-only [MCP tools] (e.g., `github_read_issue`,
`postgres_read_schema`) are allowed.
- **Planning (Write):** [`write_file`] and [`replace`] only allowed for `.md`
- **FileSystem (Read):**
[`read_file`](../tools/file-system.md#2-read_file-readfile),
[`list_directory`](../tools/file-system.md#1-list_directory-readfolder),
[`glob`](../tools/file-system.md#4-glob-findfiles)
- **Search:** [`grep_search`](../tools/file-system.md#5-grep_search-searchtext),
[`google_web_search`](../tools/web-search.md)
- **Research Subagents:**
[`codebase_investigator`](../core/subagents.md#codebase-investigator),
[`cli_help`](../core/subagents.md#cli-help-agent)
- **Interaction:** [`ask_user`](../tools/ask-user.md)
- **MCP tools (Read):** Read-only [MCP tools](../tools/mcp-server.md) (for
example, `github_read_issue`, `postgres_read_schema`) are allowed.
- **Planning (Write):**
[`write_file`](../tools/file-system.md#3-write_file-writefile) and
[`replace`](../tools/file-system.md#6-replace-edit) only allowed for `.md`
files in the `~/.gemini/tmp/<project>/<session-id>/plans/` directory or your
[custom plans directory](#custom-plan-directory-and-policies).
- **Memory:** [`save_memory`]
- **Skills:** [`activate_skill`] (allows loading specialized instructions and
resources in a read-only manner)
- **Memory:** [`save_memory`](../tools/memory.md)
- **Skills:** [`activate_skill`](../cli/skills.md) (allows loading specialized
instructions and resources in a read-only manner)
### Customizing Planning with Skills
### Custom planning with skills
You can use [Agent Skills](./skills.md) to customize how Gemini CLI approaches
planning for specific types of tasks. When a skill is activated during Plan
Mode, its specialized instructions and procedural workflows will guide the
research, design and planning phases.
You can use [Agent Skills](../cli/skills.md) to customize how Gemini CLI
approaches planning for specific types of tasks. When a skill is activated
during Plan Mode, its specialized instructions and procedural workflows will
guide the research, design, and planning phases.
For example:
@@ -165,12 +142,13 @@ To use a skill in Plan Mode, you can explicitly ask Gemini CLI to "use the
`<skill-name>` skill to plan..." or Gemini CLI may autonomously activate it
based on the task description.
### Customizing Policies
### Custom policies
Plan Mode's default tool restrictions are managed by the [policy engine] and
defined in the built-in [`plan.toml`] file. The built-in policy (Tier 1)
enforces the read-only state, but you can customize these rules by creating your
own policies in your `~/.gemini/policies/` directory (Tier 2).
Plan Mode's default tool restrictions are managed by the
[policy engine](../reference/policy-engine.md) and defined in the built-in
[`plan.toml`] file. The built-in policy (Tier 1) enforces the read-only state,
but you can customize these rules by creating your own policies in your
`~/.gemini/policies/` directory (Tier 2).
#### Example: Automatically approve read-only MCP tools
@@ -189,10 +167,13 @@ priority = 100
modes = ["plan"]
```
For more information on how the policy engine works, see the
[policy engine](../reference/policy-engine.md) docs.
#### Example: Allow git commands in Plan Mode
This rule allows you to check the repository status and see changes while in
Plan Mode.
This rule lets you check the repository status and see changes while in Plan
Mode.
`~/.gemini/policies/git-research.toml`
@@ -207,9 +188,12 @@ modes = ["plan"]
#### Example: Enable custom subagents in Plan Mode
Built-in research [subagents] like [`codebase_investigator`] and [`cli_help`]
are enabled by default in Plan Mode. You can enable additional [custom
subagents] by adding a rule to your policy.
Built-in research [subagents](../core/subagents.md) like
[`codebase_investigator`](../core/subagents.md#codebase-investigator) and
[`cli_help`](../core/subagents.md#cli-help-agent) are enabled by default in Plan
Mode. You can enable additional
[custom subagents](../core/subagents.md#creating-custom-subagents) by adding a
rule to your policy.
`~/.gemini/policies/research-subagents.toml`
@@ -224,10 +208,7 @@ modes = ["plan"]
Tell Gemini CLI it can use these tools in your prompt, for example: _"You can
check ongoing changes in git."_
For more information on how the policy engine works, see the [policy engine]
docs.
### Custom Plan Directory and Policies
### Custom plan directory and policies
By default, planning artifacts are stored in a managed temporary directory
outside your project: `~/.gemini/tmp/<project>/<session-id>/plans/`.
@@ -251,10 +232,11 @@ locations defined within a project's workspace cannot be used to escape and
overwrite sensitive files elsewhere. Any user-configured directory must reside
within the project boundary.
Using a custom directory requires updating your [policy engine] configurations
to allow `write_file` and `replace` in that specific location. For example, to
allow writing to the `.gemini/plans` directory within your project, create a
policy file at `~/.gemini/policies/plan-custom-directory.toml`:
Using a custom directory requires updating your
[policy engine](../reference/policy-engine.md) configurations to allow
`write_file` and `replace` in that specific location. For example, to allow
writing to the `.gemini/plans` directory within your project, create a policy
file at `~/.gemini/policies/plan-custom-directory.toml`:
```toml
[[rule]]
@@ -267,10 +249,69 @@ modes = ["plan"]
argsPattern = "\"file_path\":\"[^\"]+[\\\\/]+\\.gemini[\\\\/]+plans[\\\\/]+[\\w-]+\\.md\""
```
## Planning workflows
Plan Mode provides building blocks for structured research and design. These are
implemented as [extensions](../extensions/index.md) using core planning tools
like [`enter_plan_mode`](../tools/planning.md#1-enter_plan_mode-enterplanmode),
[`exit_plan_mode`](../tools/planning.md#2-exit_plan_mode-exitplanmode), and
[`ask_user`](../tools/ask-user.md).
### Built-in planning workflow
The built-in planner uses an adaptive workflow to analyze your project, consult
you on trade-offs via [`ask_user`](../tools/ask-user.md), and draft a plan for
your approval.
### Custom planning workflows
You can install or create specialized planners to suit your workflow.
#### Conductor
[Conductor] is designed for spec-driven development. It organizes work into
"tracks" and stores persistent artifacts in your project's `conductor/`
directory:
- **Automate transitions:** Switches to read-only mode via
[`enter_plan_mode`](../tools/planning.md#1-enter_plan_mode-enterplanmode).
- **Streamline decisions:** Uses [`ask_user`](../tools/ask-user.md) for
architectural choices.
- **Maintain project context:** Stores artifacts in the project directory using
[custom plan directory and policies](#custom-plan-directory-and-policies).
- **Handoff execution:** Transitions to implementation via
[`exit_plan_mode`](../tools/planning.md#2-exit_plan_mode-exitplanmode).
#### Build your own
Since Plan Mode is built on modular building blocks, you can develop your own
custom planning workflow as an [extensions](../extensions/index.md). By
leveraging core tools and [custom policies](#custom-policies), you can define
how Gemini CLI researches and stores plans for your specific domain.
To build a custom planning workflow, you can use:
- **Tool usage:** Use core tools like
[`enter_plan_mode`](../tools/planning.md#1-enter_plan_mode-enterplanmode),
[`ask_user`](../tools/ask-user.md), and
[`exit_plan_mode`](../tools/planning.md#2-exit_plan_mode-exitplanmode) to
manage the research and design process.
- **Customization:** Set your own storage locations and policy rules using
[custom plan directories](#custom-plan-directory-and-policies) and
[custom policies](#custom-policies).
> **Note:** Use [Conductor] as a reference when building your own custom
> planning workflow.
By using Plan Mode as its execution environment, your custom methodology can
enforce read-only safety during the design phase while benefiting from
high-reasoning model routing.
## Automatic Model Routing
When using an [**auto model**], Gemini CLI automatically optimizes [**model
routing**] based on the current phase of your task:
When using an [auto model](../reference/configuration.md#model), Gemini CLI
automatically optimizes [model routing](../cli/telemetry.md#model-routing) based
on the current phase of your task:
1. **Planning Phase:** While in Plan Mode, the CLI routes requests to a
high-reasoning **Pro** model to ensure robust architectural decisions and
@@ -301,7 +342,8 @@ associated plan files and task trackers.
- **Default behavior:** Sessions (and their plans) are retained for **30 days**.
- **Configuration:** You can customize this behavior via the `/settings` command
(search for **Session Retention**) or in your `settings.json` file. See
[session retention] for more details.
[session retention](../cli/session-management.md#session-retention) for more
details.
Manual deletion also removes all associated artifacts:
@@ -311,28 +353,7 @@ Manual deletion also removes all associated artifacts:
If you use a [custom plans directory](#custom-plan-directory-and-policies),
those files are not automatically deleted and must be managed manually.
[`list_directory`]: /docs/tools/file-system.md#1-list_directory-readfolder
[`read_file`]: /docs/tools/file-system.md#2-read_file-readfile
[`grep_search`]: /docs/tools/file-system.md#5-grep_search-searchtext
[`write_file`]: /docs/tools/file-system.md#3-write_file-writefile
[`glob`]: /docs/tools/file-system.md#4-glob-findfiles
[`google_web_search`]: /docs/tools/web-search.md
[`replace`]: /docs/tools/file-system.md#6-replace-edit
[MCP tools]: /docs/tools/mcp-server.md
[`save_memory`]: /docs/tools/memory.md
[`activate_skill`]: /docs/cli/skills.md
[`codebase_investigator`]: /docs/core/subagents.md#codebase_investigator
[`cli_help`]: /docs/core/subagents.md#cli_help
[subagents]: /docs/core/subagents.md
[custom subagents]: /docs/core/subagents.md#creating-custom-subagents
[policy engine]: /docs/reference/policy-engine.md
[`enter_plan_mode`]: /docs/tools/planning.md#1-enter_plan_mode-enterplanmode
[`exit_plan_mode`]: /docs/tools/planning.md#2-exit_plan_mode-exitplanmode
[`ask_user`]: /docs/tools/ask-user.md
[YOLO mode]: /docs/reference/configuration.md#command-line-arguments
[`plan.toml`]:
https://github.com/google-gemini/gemini-cli/blob/main/packages/core/src/policy/policies/plan.toml
[auto model]: /docs/reference/configuration.md#model-settings
[model routing]: /docs/cli/telemetry.md#model-routing
[preferred external editor]: /docs/reference/configuration.md#general
[session retention]: /docs/cli/session-management.md#session-retention
[Conductor]: https://github.com/gemini-cli-extensions/conductor
[open an issue]: https://github.com/google-gemini/gemini-cli/issues
+70 -1
View File
@@ -50,6 +50,74 @@ Cross-platform sandboxing with complete process isolation.
**Note**: Requires building the sandbox image locally or using a published image
from your organization's registry.
### 3. gVisor / runsc (Linux only)
Strongest isolation available: runs containers inside a user-space kernel via
[gVisor](https://github.com/google/gvisor). gVisor intercepts all container
system calls and handles them in a sandboxed kernel written in Go, providing a
strong security barrier between AI operations and the host OS.
**Prerequisites:**
- Linux (gVisor supports Linux only)
- Docker installed and running
- gVisor/runsc runtime configured
When you set `sandbox: "runsc"`, Gemini CLI runs
`docker run --runtime=runsc ...` to execute containers with gVisor isolation.
runsc is not auto-detected; you must specify it explicitly (e.g.
`GEMINI_SANDBOX=runsc` or `sandbox: "runsc"`).
To set up runsc:
1. Install the runsc binary.
2. Configure the Docker daemon to use the runsc runtime.
3. Verify the installation.
### 4. LXC/LXD (Linux only, experimental)
Full-system container sandboxing using LXC/LXD. Unlike Docker/Podman, LXC
containers run a complete Linux system with `systemd`, `snapd`, and other system
services. This is ideal for tools that don't work in standard Docker containers,
such as Snapcraft and Rockcraft.
**Prerequisites**:
- Linux only.
- LXC/LXD must be installed (`snap install lxd` or `apt install lxd`).
- A container must be created and running before starting Gemini CLI. Gemini
does **not** create the container automatically.
**Quick setup**:
```bash
# Initialize LXD (first time only)
lxd init --auto
# Create and start an Ubuntu container
lxc launch ubuntu:24.04 gemini-sandbox
# Enable LXC sandboxing
export GEMINI_SANDBOX=lxc
gemini -p "build the project"
```
**Custom container name**:
```bash
export GEMINI_SANDBOX=lxc
export GEMINI_SANDBOX_IMAGE=my-snapcraft-container
gemini -p "build the snap"
```
**Limitations**:
- Linux only (LXC is not available on macOS or Windows).
- The container must already exist and be running.
- The workspace directory is bind-mounted into the container at the same
absolute path — the path must be writable inside the container.
- Used with tools like Snapcraft or Rockcraft that require a full system.
## Quickstart
```bash
@@ -88,7 +156,8 @@ gemini -p "run the test suite"
### Enable sandboxing (in order of precedence)
1. **Command flag**: `-s` or `--sandbox`
2. **Environment variable**: `GEMINI_SANDBOX=true|docker|podman|sandbox-exec`
2. **Environment variable**:
`GEMINI_SANDBOX=true|docker|podman|sandbox-exec|runsc|lxc`
3. **Settings file**: `"sandbox": true` in the `tools` object of your
`settings.json` file (e.g., `{"tools": {"sandbox": true}}`).
+24
View File
@@ -61,6 +61,15 @@ Browser**:
/resume
```
When typing `/resume` (or `/chat`) in slash completion, commands are grouped
under titled separators:
- `-- auto --` (session browser)
- `list` is selectable and opens the session browser
- `-- checkpoints --` (manual tagged checkpoint commands)
Unique prefixes such as `/resum` and `/cha` resolve to the same grouped menu.
The Session Browser provides an interactive interface where you can perform the
following actions:
@@ -72,6 +81,21 @@ following actions:
- **Select:** Press **Enter** to resume the selected session.
- **Esc:** Press **Esc** to exit the Session Browser.
### Manual chat checkpoints
For named branch points inside a session, use chat checkpoints:
```text
/resume save decision-point
/resume list
/resume resume decision-point
```
Compatibility aliases:
- `/chat ...` works for the same commands.
- `/resume checkpoints ...` also remains supported during migration.
## Managing sessions
You can list and delete sessions to keep your history organized and manage disk
+3 -4
View File
@@ -57,7 +57,7 @@ they appear in the UI.
| 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 path in the footer. | `false` |
| Hide CWD | `ui.footer.hideCWD` | Hide the current working directory 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` |
@@ -102,7 +102,7 @@ they appear in the UI.
| UI Label | Setting | Description | Default |
| ------------------------------------ | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| Memory Discovery Max Dirs | `context.discoveryMaxDirs` | Maximum number of directories to search for memory. | `200` |
| Load Memory From Include Directories | `context.loadMemoryFromIncludeDirectories` | Controls how /memory refresh loads GEMINI.md files. When true, include directories are scanned; when false, only the current directory is used. | `false` |
| Load Memory From Include Directories | `context.loadMemoryFromIncludeDirectories` | Controls how /memory reload loads GEMINI.md files. When true, include directories are scanned; when false, only the current directory is used. | `false` |
| Respect .gitignore | `context.fileFiltering.respectGitIgnore` | Respect .gitignore files when searching. | `true` |
| Respect .geminiignore | `context.fileFiltering.respectGeminiIgnore` | Respect .geminiignore files when searching. | `true` |
| Enable Recursive File Search | `context.fileFiltering.enableRecursiveFileSearch` | Enable recursive file search functionality when completing @ references in the prompt. | `true` |
@@ -144,10 +144,9 @@ they appear in the UI.
| Enable Tool Output Masking | `experimental.toolOutputMasking.enabled` | Enables tool output masking to save tokens. | `true` |
| Use OSC 52 Paste | `experimental.useOSC52Paste` | Use OSC 52 for pasting. This may be more robust than the default system when using remote terminal sessions (if your terminal is configured to allow it). | `false` |
| Use OSC 52 Copy | `experimental.useOSC52Copy` | Use OSC 52 for copying. This may be more robust than the default system when using remote terminal sessions (if your terminal is configured to allow it). | `false` |
| Plan | `experimental.plan` | Enable planning features (Plan Mode and tools). | `false` |
| Plan | `experimental.plan` | Enable Plan Mode. | `true` |
| Model Steering | `experimental.modelSteering` | Enable model steering (user hints) to guide the model during tool execution. | `false` |
| Direct Web Fetch | `experimental.directWebFetch` | Enable web fetch behavior that bypasses LLM summarization. | `false` |
| Enable Gemma Model Router | `experimental.gemmaModelRouter.enabled` | Enable the Gemma Model Router. Requires a local endpoint serving Gemma via the Gemini API using LiteRT-LM shim. | `false` |
### Skills
+12 -11
View File
@@ -19,14 +19,15 @@ Headless mode runs Gemini CLI once and exits. It's perfect for:
## How to use headless mode
Run Gemini CLI in headless mode by providing a prompt as a positional argument.
This bypasses the interactive chat interface and prints the response to standard
output (stdout).
Run Gemini CLI in headless mode by providing a prompt with the `-p` (or
`--prompt`) flag. This bypasses the interactive chat interface and prints the
response to standard output (stdout). Positional arguments without the flag
default to interactive mode, unless the input or output is piped or redirected.
Run a single command:
```bash
gemini "Write a poem about TypeScript"
gemini -p "Write a poem about TypeScript"
```
## How to pipe input to Gemini CLI
@@ -40,19 +41,19 @@ Pipe a file:
**macOS/Linux**
```bash
cat error.log | gemini "Explain why this failed"
cat error.log | gemini -p "Explain why this failed"
```
**Windows (PowerShell)**
```powershell
Get-Content error.log | gemini "Explain why this failed"
Get-Content error.log | gemini -p "Explain why this failed"
```
Pipe a command:
```bash
git diff | gemini "Write a commit message for these changes"
git diff | gemini -p "Write a commit message for these changes"
```
## Use Gemini CLI output in scripts
@@ -78,7 +79,7 @@ one.
echo "Generating docs for $file..."
# Ask Gemini CLI to generate the documentation and print it to stdout
gemini "Generate a Markdown documentation summary for @$file. Print the
gemini -p "Generate a Markdown documentation summary for @$file. Print the
result to standard output." > "${file%.py}.md"
done
```
@@ -92,7 +93,7 @@ one.
$newName = $_.Name -replace '\.py$', '.md'
# Ask Gemini CLI to generate the documentation and print it to stdout
gemini "Generate a Markdown documentation summary for @$($_.Name). Print the result to standard output." | Out-File -FilePath $newName -Encoding utf8
gemini -p "Generate a Markdown documentation summary for @$($_.Name). Print the result to standard output." | Out-File -FilePath $newName -Encoding utf8
}
```
@@ -214,7 +215,7 @@ wrapper that writes the message for you.
# Ask Gemini to write the message
echo "Generating commit message..."
msg=$(echo "$diff" | gemini "Write a concise Conventional Commit message for this diff. Output ONLY the message.")
msg=$(echo "$diff" | gemini -p "Write a concise Conventional Commit message for this diff. Output ONLY the message.")
# Commit with the generated message
git commit -m "$msg"
@@ -251,7 +252,7 @@ wrapper that writes the message for you.
# Ask Gemini to write the message
Write-Host "Generating commit message..."
$msg = $diff | gemini "Write a concise Conventional Commit message for this diff. Output ONLY the message."
$msg = $diff | gemini -p "Write a concise Conventional Commit message for this diff. Output ONLY the message."
# Commit with the generated message
git commit -m "$msg"
+2 -2
View File
@@ -101,8 +101,8 @@ The agent will:
- **Server won't start?** Try running the docker command manually in your
terminal to see if it prints an error (e.g., "image not found").
- **Tools not found?** Run `/mcp refresh` to force the CLI to re-query the
server for its capabilities.
- **Tools not found?** Run `/mcp reload` to force the CLI to re-query the server
for its capabilities.
## Next steps
+1 -1
View File
@@ -105,7 +105,7 @@ excellent for debugging why the agent might be ignoring a rule.
If you edit a `GEMINI.md` file while a session is running, the agent won't know
immediately. Force a reload with:
**Command:** `/memory refresh`
**Command:** `/memory reload`
## Best practices
+4 -4
View File
@@ -89,9 +89,9 @@ Gemini gives you granular control over the undo process. You can choose to:
Sometimes you want to try two different approaches to the same problem.
1. Start a session and get to a decision point.
2. Save the current state with `/chat save decision-point`.
2. Save the current state with `/resume save decision-point`.
3. Try your first approach.
4. Later, use `/chat resume decision-point` to fork the conversation back to
4. Later, use `/resume resume decision-point` to fork the conversation back to
that moment and try a different approach.
This creates a new branch of history without losing your original work.
@@ -101,5 +101,5 @@ This creates a new branch of history without losing your original work.
- Learn about [Checkpointing](../../cli/checkpointing.md) to understand the
underlying safety mechanism.
- Explore [Task planning](task-planning.md) to keep complex sessions organized.
- See the [Command reference](../../reference/commands.md) for all `/chat` and
`/resume` options.
- See the [Command reference](../../reference/commands.md) for `/resume`
options, grouped checkpoint menus, and `/chat` compatibility aliases.
+4 -3
View File
@@ -17,9 +17,10 @@ prefix.
**Example:** `!ls -la`
This executes `ls -la` immediately and prints the output to your terminal. The
AI doesn't "see" this output unless you paste it back into the chat or use it in
a prompt.
This executes `ls -la` immediately and prints the output to your terminal.
Gemini CLI also records the command and its output in the current session
context, so the model can reference it in follow-up prompts. Very large outputs
may be truncated.
### Scenario: Entering Shell mode
+2 -2
View File
@@ -9,8 +9,8 @@ requests sent from `packages/cli`. For a general overview of Gemini CLI, see the
- **[Sub-agents (experimental)](./subagents.md):** Learn how to create and use
specialized sub-agents for complex tasks.
- **[Core tools API](../reference/tools-api.md):** Information on how tools are
defined, registered, and used by the core.
- **[Core tools reference](../reference/tools.md):** Information on how tools
are defined, registered, and used by the core.
- **[Memory Import Processor](../reference/memport.md):** Documentation for the
modular GEMINI.md import feature using @file.md syntax.
- **[Policy Engine](../reference/policy-engine.md):** Use the Policy Engine for
+1 -1
View File
@@ -75,7 +75,7 @@ Markdown file.
Users can manage subagents using the following commands within the Gemini CLI:
- `/agents list`: Displays all available local and remote subagents.
- `/agents refresh`: Reloads the agent registry. Use this after adding or
- `/agents reload`: Reloads the agent registry. Use this after adding or
modifying agent definition files.
- `/agents enable <agent_name>`: Enables a specific subagent.
- `/agents disable <agent_name>`: Disables a specific subagent.
+1 -1
View File
@@ -297,7 +297,7 @@ Gemini CLI can also delegate tasks to remote subagents using the Agent-to-Agent
> **Note: Remote subagents are currently an experimental feature.**
See the [Remote Subagents documentation](/docs/core/remote-agents) for detailed
See the [Remote Subagents documentation](remote-agents) for detailed
configuration and usage instructions.
## Extension subagents
+4
View File
@@ -4,6 +4,10 @@ To use Gemini CLI, you'll need to authenticate with Google. This guide helps you
quickly find the best way to sign in based on your account type and how you're
using the CLI.
> **Note:** Looking for a high-level comparison of all available subscriptions?
> To compare features and find the right quota for your needs, see our
> [Plans page](https://geminicli.com/plans/).
For most users, we recommend starting Gemini CLI and logging in with your
personal Google account.
+4
View File
@@ -39,6 +39,10 @@ When you encounter that limit, youll be given the option to switch to Gemini
2.5 Pro, upgrade for higher limits, or stop. Youll also be told when your usage
limit resets and Gemini 3 Pro can be used again.
> **Note:** Looking to upgrade for higher limits? To compare subscription
> options and find the right quota for your needs, see our
> [Plans page](https://geminicli.com/plans/).
Similarly, when you reach your daily usage limit for Gemini 2.5 Pro, youll see
a message prompting fallback to Gemini 2.5 Flash.
+1 -1
View File
@@ -72,7 +72,7 @@ session's token usage, as well as your overall quota and usage for the supported
models.
For more information on the `/stats` command and its subcommands, see the
[Command Reference](../../reference/commands.md#stats).
[Command Reference](../reference/commands.md#stats).
## Next steps
+1 -1
View File
@@ -70,7 +70,7 @@ gemini
```
For a list of options and additional commands, see the
[CLI cheatsheet](/docs/cli/cli-reference.md).
[CLI cheatsheet](../cli/cli-reference.md).
You can also run Gemini CLI using one of the following advanced methods:
+1 -1
View File
@@ -449,7 +449,7 @@ When you open a project with hooks defined in `.gemini/settings.json`:
Hooks inherit the environment of the Gemini CLI process, which may include
sensitive API keys. Gemini CLI provides a
[redaction system](/docs/reference/configuration.md#environment-variable-redaction)
[redaction system](../reference/configuration.md#environment-variable-redaction)
that automatically filters variables matching sensitive patterns (e.g., `KEY`,
`TOKEN`).
+6 -6
View File
@@ -22,11 +22,11 @@ With hooks, you can:
### Getting started
- **[Writing hooks guide](/docs/hooks/writing-hooks)**: A tutorial on creating
your first hook with comprehensive examples.
- **[Best practices](/docs/hooks/best-practices)**: Guidelines on security,
- **[Writing hooks guide](../hooks/writing-hooks)**: A tutorial on creating your
first hook with comprehensive examples.
- **[Best practices](../hooks/best-practices)**: Guidelines on security,
performance, and debugging.
- **[Hooks reference](/docs/hooks/reference)**: The definitive technical
- **[Hooks reference](../hooks/reference)**: The definitive technical
specification of I/O schemas and exit codes.
## Core concepts
@@ -152,8 +152,8 @@ Gemini CLI **fingerprints** project hooks. If a hook's name or command changes
(e.g., via `git pull`), it is treated as a **new, untrusted hook** and you will
be warned before it executes.
See [Security Considerations](/docs/hooks/best-practices#using-hooks-securely)
for a detailed threat model.
See [Security Considerations](../hooks/best-practices#using-hooks-securely) for
a detailed threat model.
## Managing hooks
+2 -2
View File
@@ -82,8 +82,8 @@ For `BeforeTool` and `AfterTool` events, the `matcher` field in your settings is
compared against the name of the tool being executed.
- **Built-in Tools**: You can match any built-in tool (e.g., `read_file`,
`run_shell_command`). See the [Tools Reference](/docs/tools) for a full list
of available tool names.
`run_shell_command`). See the [Tools Reference](../reference/tools) for a full
list of available tool names.
- **MCP Tools**: Tools from MCP servers follow the naming pattern
`mcp__<server_name>__<tool_name>`.
- **Regex Support**: Matchers support regular expressions (e.g.,
+2 -2
View File
@@ -108,8 +108,8 @@ Deep technical documentation and API specifications.
processes memory from various sources.
- **[Policy engine](./reference/policy-engine.md):** Fine-grained execution
control.
- **[Tools API](./reference/tools-api.md):** The API for defining and using
tools.
- **[Tools reference](./reference/tools.md):** Information on how tools are
defined, registered, and used.
## Resources
+39 -1
View File
@@ -113,7 +113,45 @@ process.
ensure every issue is eventually categorized, even if the initial triage
fails.
### 5. Release automation
### 5. Automatic unassignment of inactive contributors: `Unassign Inactive Issue Assignees`
To keep the list of open `help wanted` issues accessible to all contributors,
this workflow automatically removes **external contributors** who have not
opened a linked pull request within **7 days** of being assigned. Maintainers,
org members, and repo collaborators with write access or above are always exempt
and will never be auto-unassigned.
- **Workflow File**: `.github/workflows/unassign-inactive-assignees.yml`
- **When it runs**: Every day at 09:00 UTC, and can be triggered manually with
an optional `dry_run` mode.
- **What it does**:
1. Finds every open issue labeled `help wanted` that has at least one
assignee.
2. Identifies privileged users (team members, repo collaborators with write+
access, maintainers) and skips them entirely.
3. For each remaining (external) assignee it reads the issue's timeline to
determine:
- The exact date they were assigned (using `assigned` timeline events).
- Whether they have opened a PR that is already linked/cross-referenced to
the issue.
4. Each cross-referenced PR is fetched to verify it is **ready for review**:
open and non-draft, or already merged. Draft PRs do not count.
5. If an assignee has been assigned for **more than 7 days** and no qualifying
PR is found, they are automatically unassigned and a comment is posted
explaining the reason and how to re-claim the issue.
6. Assignees who have a non-draft, open or merged PR linked to the issue are
**never** unassigned by this workflow.
- **What you should do**:
- **Open a real PR, not a draft**: Within 7 days of being assigned, open a PR
that is ready for review and include `Fixes #<issue-number>` in the
description. Draft PRs do not satisfy the requirement and will not prevent
auto-unassignment.
- **Re-assign if unassigned by mistake**: Comment `/assign` on the issue to
assign yourself again.
- **Unassign yourself** if you can no longer work on the issue by commenting
`/unassign`, so other contributors can pick it up right away.
### 6. Release automation
This workflow handles the process of packaging and publishing new versions of
the Gemini CLI.
+2 -1
View File
@@ -8,7 +8,8 @@
"/docs/core/concepts": "/docs",
"/docs/core/memport": "/docs/reference/memport",
"/docs/core/policy-engine": "/docs/reference/policy-engine",
"/docs/core/tools-api": "/docs/reference/tools-api",
"/docs/core/tools-api": "/docs/reference/tools",
"/docs/reference/tools-api": "/docs/reference/tools",
"/docs/faq": "/docs/resources/faq",
"/docs/get-started/configuration": "/docs/reference/configuration",
"/docs/get-started/configuration-v1": "/docs/reference/configuration",
+49 -9
View File
@@ -28,24 +28,33 @@ Slash commands provide meta-level control over the CLI itself.
### `/chat`
- **Description:** Save and resume conversation history for branching
conversation state interactively, or resuming a previous state from a later
session.
- **Description:** Alias for `/resume`. Both commands now expose the same
session browser action and checkpoint subcommands.
- **Menu layout when typing `/chat` (or `/resume`)**:
- `-- auto --`
- `list` (selecting this opens the auto-saved session browser)
- `-- checkpoints --`
- `list`, `save`, `resume`, `delete`, `share` (manual tagged checkpoints)
- **Note:** Unique prefixes (for example `/cha` or `/resum`) resolve to the
same grouped menu.
- **Sub-commands:**
- **`debug`**
- **Description:** Export the most recent API request as a JSON payload.
- **`delete <tag>`**
- **Description:** Deletes a saved conversation checkpoint.
- **Equivalent:** `/resume delete <tag>`
- **`list`**
- **Description:** Lists available tags for chat state resumption.
- **Description:** Lists available tags for manually saved checkpoints.
- **Note:** This command only lists chats saved within the current project.
Because chat history is project-scoped, chats saved in other project
directories will not be displayed.
- **Equivalent:** `/resume list`
- **`resume <tag>`**
- **Description:** Resumes a conversation from a previous save.
- **Note:** You can only resume chats that were saved within the current
project. To resume a chat from a different project, you must run the
Gemini CLI from that project's directory.
- **Equivalent:** `/resume resume <tag>`
- **`save <tag>`**
- **Description:** Saves the current conversation history. You must add a
`<tag>` for identifying the conversation state.
@@ -60,10 +69,12 @@ Slash commands provide meta-level control over the CLI itself.
conversation states. For automatic checkpoints created before file
modifications, see the
[Checkpointing documentation](../cli/checkpointing.md).
- **Equivalent:** `/resume save <tag>`
- **`share [filename]`**
- **Description** Writes the current conversation to a provided Markdown or
JSON file. If no filename is provided, then the CLI will generate one.
- **Usage** `/chat share file.md` or `/chat share file.json`.
- **Equivalent:** `/resume share [filename]`
### `/clear`
@@ -268,8 +279,11 @@ Slash commands provide meta-level control over the CLI itself.
- **Description:** Switch to Plan Mode (read-only) and view the current plan if
one has been generated.
- **Note:** This feature requires the `experimental.plan` setting to be
enabled in your configuration.
- **Note:** This feature is enabled by default. It can be disabled via the
`experimental.plan` setting in your configuration.
- **Sub-commands:**
- **`copy`**:
- **Description:** Copy the currently approved plan to your clipboard.
### `/policies`
@@ -311,10 +325,13 @@ Slash commands provide meta-level control over the CLI itself.
### `/resume`
- **Description:** Browse and resume previous conversation sessions. Opens an
interactive session browser where you can search, filter, and select from
automatically saved conversations.
- **Description:** Browse and resume previous conversation sessions, and manage
manual chat checkpoints.
- **Features:**
- **Auto sessions:** Run `/resume` to open the interactive session browser for
automatically saved conversations.
- **Chat checkpoints:** Use checkpoint subcommands directly (`/resume save`,
`/resume resume`, etc.).
- **Management:** Delete unwanted sessions directly from the browser
- **Resume:** Select any session to resume and continue the conversation
- **Search:** Use `/` to search through conversation content across all
@@ -325,6 +342,23 @@ Slash commands provide meta-level control over the CLI itself.
- **Note:** All conversations are automatically saved as you chat - no manual
saving required. See [Session Management](../cli/session-management.md) for
complete details.
- **Alias:** `/chat` provides the same behavior and subcommands.
- **Sub-commands:**
- **`list`**
- **Description:** Lists available tags for manual chat checkpoints.
- **`save <tag>`**
- **Description:** Saves the current conversation as a tagged checkpoint.
- **`resume <tag>`** (alias: `load`)
- **Description:** Loads a previously saved tagged checkpoint.
- **`delete <tag>`**
- **Description:** Deletes a tagged checkpoint.
- **`share [filename]`**
- **Description:** Exports the current conversation to Markdown or JSON.
- **`debug`**
- **Description:** Export the most recent API request as JSON payload
(nightly builds).
- **Compatibility alias:** `/resume checkpoints ...` is still accepted for the
same checkpoint commands.
### `/settings`
@@ -405,6 +439,12 @@ Slash commands provide meta-level control over the CLI itself.
- **`nodesc`** or **`nodescriptions`**:
- **Description:** Hide tool descriptions, showing only the tool names.
### `/upgrade`
- **Description:** Open the Gemini Code Assist upgrade page in your browser.
This lets you upgrade your tier for higher usage limits.
- **Note:** This command is only available when logged in with Google.
### `/vim`
- **Description:** Toggle vim mode on or off. When vim mode is enabled, the
+23 -7
View File
@@ -250,8 +250,18 @@ their corresponding top-level category object in your `settings.json` file.
input.
- **Default:** `false`
- **`ui.footer.items`** (array):
- **Description:** List of item IDs to display in the footer. Rendered in
order
- **Default:** `undefined`
- **`ui.footer.showLabels`** (boolean):
- **Description:** Display a second line above the footer items with
descriptive headers (e.g., /model).
- **Default:** `true`
- **`ui.footer.hideCWD`** (boolean):
- **Description:** Hide the current working directory path in the footer.
- **Description:** Hide the current working directory in the footer.
- **Default:** `false`
- **`ui.footer.hideSandboxStatus`** (boolean):
@@ -709,7 +719,7 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `[]`
- **`context.loadMemoryFromIncludeDirectories`** (boolean):
- **Description:** Controls how /memory refresh loads GEMINI.md files. When
- **Description:** Controls how /memory reload loads GEMINI.md files. When
true, include directories are scanned; when false, only the current
directory is used.
- **Default:** `false`
@@ -747,7 +757,8 @@ their corresponding top-level category object in your `settings.json` file.
- **`tools.sandbox`** (boolean | string):
- **Description:** Sandbox execution environment. Set to a boolean to enable
or disable the sandbox, or provide a string path to a sandbox profile.
or disable the sandbox, provide a string path to a sandbox profile, or
specify an explicit sandbox command (e.g., "docker", "podman", "lxc").
- **Default:** `undefined`
- **Requires restart:** Yes
@@ -1010,7 +1021,12 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `false`
- **`experimental.plan`** (boolean):
- **Description:** Enable planning features (Plan Mode and tools).
- **Description:** Enable Plan Mode.
- **Default:** `true`
- **Requires restart:** Yes
- **`experimental.taskTracker`** (boolean):
- **Description:** Enable task tracker tools.
- **Default:** `false`
- **Requires restart:** Yes
@@ -1025,8 +1041,8 @@ their corresponding top-level category object in your `settings.json` file.
- **Requires restart:** Yes
- **`experimental.gemmaModelRouter.enabled`** (boolean):
- **Description:** Enable the Gemma Model Router. Requires a local endpoint
serving Gemma via the Gemini API using LiteRT-LM shim.
- **Description:** Enable the Gemma Model Router (experimental). Requires a
local endpoint serving Gemma via the Gemini API using LiteRT-LM shim.
- **Default:** `false`
- **Requires restart:** Yes
@@ -1689,7 +1705,7 @@ conventions and context.
loaded, allowing you to verify the hierarchy and content being used by the
AI.
- See the [Commands documentation](./commands.md#memory) for full details on
the `/memory` command and its sub-commands (`show` and `refresh`).
the `/memory` command and its sub-commands (`show` and `reload`).
By understanding and utilizing these configuration layers and the hierarchical
nature of context files, you can effectively manage the AI's memory and tailor
+90 -90
View File
@@ -8,119 +8,119 @@ available combinations.
#### Basic Controls
| 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` |
| 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
| Action | Keys |
| ------------------------------------------- | ------------------------------------------------------------ |
| Move the cursor to the start of the line. | `Ctrl + A`<br />`Home (no Shift, Ctrl)` |
| Move the cursor to the end of the line. | `Ctrl + E`<br />`End (no Shift, Ctrl)` |
| Move the cursor up one line. | `Up Arrow (no Shift, Alt, Ctrl, Cmd)` |
| Move the cursor down one line. | `Down Arrow (no Shift, Alt, Ctrl, Cmd)` |
| Move the cursor one character to the left. | `Left Arrow (no Shift, Alt, Ctrl, Cmd)` |
| Move the cursor one character to the right. | `Right Arrow (no Shift, Alt, Ctrl, Cmd)`<br />`Ctrl + F` |
| Move the cursor one word to the left. | `Ctrl + Left Arrow`<br />`Alt + Left Arrow`<br />`Alt + B` |
| Move the cursor one word to the right. | `Ctrl + Right Arrow`<br />`Alt + Right Arrow`<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
| 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 + Z (no Shift)`<br />`Alt + Z (no Shift)` |
| Redo the most recent undone text edit. | `Shift + Ctrl + Z`<br />`Shift + Cmd + Z`<br />`Shift + Alt + 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
| Action | Keys |
| ------------------------ | --------------------------------- |
| Scroll content up. | `Shift + Up Arrow` |
| Scroll content down. | `Shift + Down Arrow` |
| 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` |
| 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
| Action | Keys |
| -------------------------------------------- | --------------------- |
| Show the previous entry in history. | `Ctrl + P (no Shift)` |
| Show the next entry in history. | `Ctrl + N (no Shift)` |
| Start reverse search through history. | `Ctrl + R` |
| Submit the selected reverse-search match. | `Enter (no Ctrl)` |
| Accept a suggestion while reverse searching. | `Tab (no Shift)` |
| Browse and rewind previous interactions. | `Double Esc` |
| 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` |
| Browse and rewind previous interactions. | `Double Esc` |
#### Navigation
| Action | Keys |
| -------------------------------------------------- | ------------------------------------------- |
| Move selection up in lists. | `Up Arrow (no Shift)` |
| Move selection down in lists. | `Down Arrow (no Shift)` |
| Move up within dialog options. | `Up Arrow (no Shift)`<br />`K (no Shift)` |
| Move down within dialog options. | `Down Arrow (no Shift)`<br />`J (no Shift)` |
| Move to the next item or question in a dialog. | `Tab (no Shift)` |
| 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
| Action | Keys |
| --------------------------------------- | -------------------------------------------------- |
| Accept the inline suggestion. | `Tab (no Shift)`<br />`Enter (no Ctrl)` |
| Move to the previous completion option. | `Up Arrow (no Shift)`<br />`Ctrl + P (no Shift)` |
| Move to the next completion option. | `Down Arrow (no Shift)`<br />`Ctrl + N (no Shift)` |
| Expand an inline suggestion. | `Right Arrow` |
| Collapse an inline suggestion. | `Left Arrow` |
| 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
| Action | Keys |
| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| Submit the current prompt. | `Enter (no Shift, Alt, Ctrl, Cmd)` |
| Insert a newline without submitting. | `Ctrl + Enter`<br />`Cmd + 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 + 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
| 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` |
| Toggle current background shell visibility. | `Ctrl + B` |
| Toggle background shell list. | `Ctrl + L` |
| Kill the active background shell. | `Ctrl + K` |
| Confirm selection in background shell list. | `Enter` |
| Dismiss background shell list. | `Esc` |
| Move focus from background shell to Gemini. | `Shift + Tab` |
| Move focus from background shell list to Gemini. | `Tab (no Shift)` |
| Show warning when trying to move focus away from background shell. | `Tab (no Shift)` |
| Show warning when trying to move focus away from shell input. | `Tab (no Shift)` |
| Move focus from Gemini to the active shell. | `Tab (no Shift)` |
| Move focus from the shell back to Gemini. | `Shift + Tab` |
| Clear the terminal screen and redraw the UI. | `Ctrl + L` |
| Restart the application. | `R` |
| Suspend the CLI and move it to the background. | `Ctrl + Z` |
| 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` |
| Toggle current background shell visibility. | `Ctrl+B` |
| Toggle background shell list. | `Ctrl+L` |
| Kill the active background shell. | `Ctrl+K` |
| Confirm selection in background shell list. | `Enter` |
| Dismiss background shell list. | `Esc` |
| 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` |
| Show warning when trying to move focus away from shell input. | `Tab` |
| 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` |
<!-- KEYBINDINGS-AUTOGEN:END -->
@@ -156,7 +156,7 @@ available combinations.
## Limitations
- On [Windows Terminal](https://en.wikipedia.org/wiki/Windows_Terminal):
- `shift+enter` is not supported.
- `shift+enter` is only supported in version 1.25 and higher.
- `shift+tab`
[is not supported](https://github.com/google-gemini/gemini-cli/issues/20314)
on Node 20 and earlier versions of Node 22.
+14 -5
View File
@@ -91,10 +91,17 @@ the arguments don't match the pattern, the rule does not apply.
There are three possible decisions a rule can enforce:
- `allow`: The tool call is executed automatically without user interaction.
- `deny`: The tool call is blocked and is not executed.
- `deny`: The tool call is blocked and is not executed. For global rules (those
without an `argsPattern`), tools that are denied are **completely excluded
from the model's memory**. This means the model will not even see the tool as
an option, which is more secure and saves context window space.
- `ask_user`: The user is prompted to approve or deny the tool call. (In
non-interactive mode, this is treated as `deny`.)
> **Note:** The `deny` decision is the recommended way to exclude tools. The
> legacy `tools.exclude` setting in `settings.json` is deprecated in favor of
> policy rules with a `deny` decision.
### Priority system and tiers
The policy engine uses a sophisticated priority system to resolve conflicts when
@@ -143,8 +150,8 @@ always active.
confirmation.
- `autoEdit`: Optimized for automated code editing; some write tools may be
auto-approved.
- `plan`: A strict, read-only mode for research and design. See [Customizing
Plan Mode Policies].
- `plan`: A strict, read-only mode for research and design. See
[Customizing Plan Mode Policies](../cli/plan-mode.md#customizing-policies).
- `yolo`: A mode where all tools are auto-approved (use with extreme caution).
## Rule matching
@@ -212,6 +219,10 @@ Here is a breakdown of the fields available in a TOML policy rule:
# A unique name for the tool, or an array of names.
toolName = "run_shell_command"
# (Optional) The name of a subagent. If provided, the rule only applies to tool calls
# made by this specific subagent.
subagent = "generalist"
# (Optional) The name of an MCP server. Can be combined with toolName
# to form a composite name like "mcpName__toolName".
mcpName = "my-custom-server"
@@ -360,5 +371,3 @@ out-of-the-box experience.
- In **`yolo`** mode, a high-priority rule allows all tools.
- In **`autoEdit`** mode, rules allow certain write operations to happen without
prompting.
[Customizing Plan Mode Policies]: /docs/cli/plan-mode.md#customizing-policies
-131
View File
@@ -1,131 +0,0 @@
# Gemini CLI core: Tools API
The Gemini CLI core (`packages/core`) features a robust system for defining,
registering, and executing tools. These tools extend the capabilities of the
Gemini model, allowing it to interact with the local environment, fetch web
content, and perform various actions beyond simple text generation.
## Core concepts
- **Tool (`tools.ts`):** An interface and base class (`BaseTool`) that defines
the contract for all tools. Each tool must have:
- `name`: A unique internal name (used in API calls to Gemini).
- `displayName`: A user-friendly name.
- `description`: A clear explanation of what the tool does, which is provided
to the Gemini model.
- `parameterSchema`: A JSON schema defining the parameters that the tool
accepts. This is crucial for the Gemini model to understand how to call the
tool correctly.
- `validateToolParams()`: A method to validate incoming parameters.
- `getDescription()`: A method to provide a human-readable description of what
the tool will do with specific parameters before execution.
- `shouldConfirmExecute()`: A method to determine if user confirmation is
required before execution (e.g., for potentially destructive operations).
- `execute()`: The core method that performs the tool's action and returns a
`ToolResult`.
- **`ToolResult` (`tools.ts`):** An interface defining the structure of a tool's
execution outcome:
- `llmContent`: The factual content to be included in the history sent back to
the LLM for context. This can be a simple string or a `PartListUnion` (an
array of `Part` objects and strings) for rich content.
- `returnDisplay`: A user-friendly string (often Markdown) or a special object
(like `FileDiff`) for display in the CLI.
- **Returning rich content:** Tools are not limited to returning simple text.
The `llmContent` can be a `PartListUnion`, which is an array that can contain
a mix of `Part` objects (for images, audio, etc.) and `string`s. This allows a
single tool execution to return multiple pieces of rich content.
- **Tool registry (`tool-registry.ts`):** A class (`ToolRegistry`) responsible
for:
- **Registering tools:** Holding a collection of all available built-in tools
(e.g., `ReadFileTool`, `ShellTool`).
- **Discovering tools:** It can also discover tools dynamically:
- **Command-based discovery:** If `tools.discoveryCommand` is configured in
settings, this command is executed. It's expected to output JSON
describing custom tools, which are then registered as `DiscoveredTool`
instances.
- **MCP-based discovery:** If `mcp.serverCommand` is configured, the
registry can connect to a Model Context Protocol (MCP) server to list and
register tools (`DiscoveredMCPTool`).
- **Providing schemas:** Exposing the `FunctionDeclaration` schemas of all
registered tools to the Gemini model, so it knows what tools are available
and how to use them.
- **Retrieving tools:** Allowing the core to get a specific tool by name for
execution.
## Built-in tools
The core comes with a suite of pre-defined tools, typically found in
`packages/core/src/tools/`. These include:
- **File system tools:**
- `LSTool` (`ls.ts`): Lists directory contents.
- `ReadFileTool` (`read-file.ts`): Reads the content of a single file.
- `WriteFileTool` (`write-file.ts`): Writes content to a file.
- `GrepTool` (`grep.ts`): Searches for patterns in files.
- `GlobTool` (`glob.ts`): Finds files matching glob patterns.
- `EditTool` (`edit.ts`): Performs in-place modifications to files (often
requiring confirmation).
- `ReadManyFilesTool` (`read-many-files.ts`): Reads and concatenates content
from multiple files or glob patterns (used by the `@` command in CLI).
- **Execution tools:**
- `ShellTool` (`shell.ts`): Executes arbitrary shell commands (requires
careful sandboxing and user confirmation).
- **Web tools:**
- `WebFetchTool` (`web-fetch.ts`): Fetches content from a URL.
- `WebSearchTool` (`web-search.ts`): Performs a web search.
- **Memory tools:**
- `MemoryTool` (`memoryTool.ts`): Interacts with the AI's memory.
Each of these tools extends `BaseTool` and implements the required methods for
its specific functionality.
## Tool execution flow
1. **Model request:** The Gemini model, based on the user's prompt and the
provided tool schemas, decides to use a tool and returns a `FunctionCall`
part in its response, specifying the tool name and arguments.
2. **Core receives request:** The core parses this `FunctionCall`.
3. **Tool retrieval:** It looks up the requested tool in the `ToolRegistry`.
4. **Parameter validation:** The tool's `validateToolParams()` method is
called.
5. **Confirmation (if needed):**
- The tool's `shouldConfirmExecute()` method is called.
- If it returns details for confirmation, the core communicates this back to
the CLI, which prompts the user.
- The user's decision (e.g., proceed, cancel) is sent back to the core.
6. **Execution:** If validated and confirmed (or if no confirmation is needed),
the core calls the tool's `execute()` method with the provided arguments and
an `AbortSignal` (for potential cancellation).
7. **Result processing:** The `ToolResult` from `execute()` is received by the
core.
8. **Response to model:** The `llmContent` from the `ToolResult` is packaged as
a `FunctionResponse` and sent back to the Gemini model so it can continue
generating a user-facing response.
9. **Display to user:** The `returnDisplay` from the `ToolResult` is sent to
the CLI to show the user what the tool did.
## Extending with custom tools
While direct programmatic registration of new tools by users isn't explicitly
detailed as a primary workflow in the provided files for typical end-users, the
architecture supports extension through:
- **Command-based discovery:** Advanced users or project administrators can
define a `tools.discoveryCommand` in `settings.json`. This command, when run
by the Gemini CLI core, should output a JSON array of `FunctionDeclaration`
objects. The core will then make these available as `DiscoveredTool`
instances. The corresponding `tools.callCommand` would then be responsible for
actually executing these custom tools.
- **MCP server(s):** For more complex scenarios, one or more MCP servers can be
set up and configured via the `mcpServers` setting in `settings.json`. The
Gemini CLI core can then discover and use tools exposed by these servers. As
mentioned, if you have multiple MCP servers, the tool names will be prefixed
with the server name from your configuration (e.g.,
`serverAlias__actualToolName`).
This tool system provides a flexible and powerful way to augment the Gemini
model's capabilities, making the Gemini CLI a versatile assistant for a wide
range of tasks.
+106
View File
@@ -0,0 +1,106 @@
# Tools reference
Gemini CLI uses tools to interact with your local environment, access
information, and perform actions on your behalf. These tools extend the model's
capabilities beyond text generation, letting it read files, execute commands,
and search the web.
## How to use Gemini CLI's tools
Tools are generally invoked automatically by Gemini CLI when it needs to perform
an action. However, you can also trigger specific tools manually using shorthand
syntax.
### Automatic execution and security
When the model wants to use a tool, Gemini CLI evaluates the request against its
security policies.
- **User confirmation:** You must manually approve tools that modify files or
execute shell commands (mutators). The CLI shows you a diff or the exact
command before you confirm.
- **Sandboxing:** You can run tool executions in secure, containerized
environments to isolate changes from your host system. For more details, see
the [Sandboxing](../cli/sandbox.md) guide.
- **Trusted folders:** You can configure which directories allow the model to
use system tools. For more details, see the
[Trusted folders](../cli/trusted-folders.md) guide.
Review confirmation prompts carefully before allowing a tool to execute.
### How to use manually-triggered tools
You can directly trigger key tools using special syntax in your prompt:
- **[File access](../tools/file-system.md#read_many_files) (`@`):** Use the `@`
symbol followed by a file or directory path to include its content in your
prompt. This triggers the `read_many_files` tool.
- **[Shell commands](../tools/shell.md) (`!`):** Use the `!` symbol followed by
a system command to execute it directly. This triggers the `run_shell_command`
tool.
## How to manage tools
Using built-in commands, you can inspect available tools and configure how they
behave.
### Tool discovery
Use the `/tools` command to see what tools are currently active in your session.
- **`/tools`**: Lists all registered tools with their display names.
- **`/tools desc`**: Lists all tools with their full descriptions.
This is especially useful for verifying that
[MCP servers](../tools/mcp-server.md) or custom tools are loaded correctly.
### Tool configuration
You can enable, disable, or configure specific tools in your settings. For
example, you can set a specific pager for shell commands or configure the
browser used for web searches. See the [Settings](../cli/settings.md) guide for
details.
## Available tools
The following table lists all available tools, categorized by their primary
function.
| Category | Tool | Kind | Description |
| :---------- | :----------------------------------------------- | :------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Execution | [`run_shell_command`](../tools/shell.md) | `Execute` | Executes arbitrary shell commands. Supports interactive sessions and background processes. Requires manual confirmation.<br><br>**Parameters:** `command`, `description`, `dir_path`, `is_background` |
| File System | [`glob`](../tools/file-system.md) | `Search` | Finds files matching specific glob patterns across the workspace.<br><br>**Parameters:** `pattern`, `dir_path`, `case_sensitive`, `respect_git_ignore`, `respect_gemini_ignore` |
| File System | [`grep_search`](../tools/file-system.md) | `Search` | Searches for a regular expression pattern within file contents. Legacy alias: `search_file_content`.<br><br>**Parameters:** `pattern`, `dir_path`, `include`, `exclude_pattern`, `names_only`, `max_matches_per_file`, `total_max_matches` |
| File System | [`list_directory`](../tools/file-system.md) | `Read` | Lists the names of files and subdirectories within a specified path.<br><br>**Parameters:** `dir_path`, `ignore`, `file_filtering_options` |
| File System | [`read_file`](../tools/file-system.md) | `Read` | Reads the content of a specific file. Supports text, images, audio, and PDF.<br><br>**Parameters:** `file_path`, `start_line`, `end_line` |
| File System | [`read_many_files`](../tools/file-system.md) | `Read` | Reads and concatenates content from multiple files. Often triggered by the `@` symbol in your prompt.<br><br>**Parameters:** `include`, `exclude`, `recursive`, `useDefaultExcludes`, `file_filtering_options` |
| File System | [`replace`](../tools/file-system.md) | `Edit` | Performs precise text replacement within a file. Requires manual confirmation.<br><br>**Parameters:** `file_path`, `instruction`, `old_string`, `new_string`, `allow_multiple` |
| File System | [`write_file`](../tools/file-system.md) | `Edit` | Creates or overwrites a file with new content. Requires manual confirmation.<br><br>**Parameters:** `file_path`, `content` |
| Interaction | [`ask_user`](../tools/ask-user.md) | `Communicate` | Requests clarification or missing information via an interactive dialog.<br><br>**Parameters:** `questions` |
| Interaction | [`write_todos`](../tools/todos.md) | `Other` | Maintains an internal list of subtasks. The model uses this to track its own progress and display it to you.<br><br>**Parameters:** `todos` |
| Memory | [`activate_skill`](../tools/activate-skill.md) | `Other` | Loads specialized procedural expertise for specific tasks from the `.gemini/skills` directory.<br><br>**Parameters:** `name` |
| Memory | [`get_internal_docs`](../tools/internal-docs.md) | `Think` | Accesses Gemini CLI's own documentation to provide more accurate answers about its capabilities.<br><br>**Parameters:** `path` |
| Memory | [`save_memory`](../tools/memory.md) | `Think` | Persists specific facts and project details to your `GEMINI.md` file to retain context.<br><br>**Parameters:** `fact` |
| Planning | [`enter_plan_mode`](../tools/planning.md) | `Plan` | Switches the CLI to a safe, read-only "Plan Mode" for researching complex changes.<br><br>**Parameters:** `reason` |
| Planning | [`exit_plan_mode`](../tools/planning.md) | `Plan` | Finalizes a plan, presents it for review, and requests approval to start implementation.<br><br>**Parameters:** `plan` |
| System | `complete_task` | `Other` | Finalizes a subagent's mission and returns the result to the parent agent. This tool is not available to the user.<br><br>**Parameters:** `result` |
| Web | [`google_web_search`](../tools/web-search.md) | `Search` | Performs a Google Search to find up-to-date information.<br><br>**Parameters:** `query` |
| Web | [`web_fetch`](../tools/web-fetch.md) | `Fetch` | Retrieves and processes content from specific URLs. **Warning:** This tool can access local and private network addresses (e.g., localhost), which may pose a security risk if used with untrusted prompts.<br><br>**Parameters:** `prompt` |
## Under the hood
For developers, the tool system is designed to be extensible and robust. The
`ToolRegistry` class manages all available tools.
You can extend Gemini CLI with custom tools by configuring
`tools.discoveryCommand` in your settings or by connecting to MCP servers.
> **Note:** For a deep dive into the internal Tool API and how to implement your
> own tools in the codebase, see the `packages/core/src/tools/` directory in
> GitHub.
## Next steps
- Learn how to [Set up an MCP server](../tools/mcp-server.md).
- Explore [Agent Skills](../cli/skills.md) for specialized expertise.
- See the [Command reference](./commands.md) for slash commands.
+72 -38
View File
@@ -1,14 +1,13 @@
# Gemini CLI: Quotas and pricing
Gemini CLI offers a generous free tier that covers many individual developers'
use cases. For enterprise or professional usage, or if you need higher limits,
use cases. For enterprise or professional usage, or if you need increased quota,
several options are available depending on your authentication account type.
See [privacy and terms](./tos-privacy.md) for details on the Privacy Policy and
Terms of Service.
For a high-level comparison of available subscriptions and to select the right
quota for your needs, see the [Plans page](https://geminicli.com/plans/).
> **Note:** Published prices are list price; additional negotiated commercial
> discounting may apply.
## Overview
This article outlines the specific quotas and pricing applicable to Gemini CLI
when using different authentication methods.
@@ -23,10 +22,11 @@ Generally, there are three categories to choose from:
## Free usage
Your journey begins with a generous free tier, perfect for experimentation and
light use.
Access to Gemini CLI begins with a generous free tier, perfect for
experimentation and light use.
Your free usage limits depend on your authorization type.
Your free usage is governed by the following limits, which depend on your
authorization type.
### Log in with Google (Gemini Code Assist for individuals)
@@ -69,6 +69,19 @@ Learn more at
If you use up your initial number of requests, you can continue to benefit from
Gemini CLI by upgrading to one of the following subscriptions:
### Individuals
These tiers apply when you sign in with a personal account. To verify whether
you're on a personal account, visit
[Google One](https://one.google.com/about/plans?hl=en-US&g1_landing_page=0):
- If you are on a personal account, you will see your personal dashboard.
- If you are not on a personal account, you will see: "You're currently signed
in to your Google Workspace Account."
**Supported tiers:** _- Tiers not listed above, including Google AI Plus, are
not supported._
- [Google AI Pro and AI Ultra](https://gemini.google/subscriptions/). This is
recommended for individual developers. Quotas and pricing are based on a fixed
price subscription.
@@ -78,14 +91,26 @@ Gemini CLI by upgrading to one of the following subscriptions:
Learn more at
[Gemini Code Assist Quotas and Limits](https://developers.google.com/gemini-code-assist/resources/quotas)
- [Purchase a Gemini Code Assist Subscription through Google Cloud ](https://cloud.google.com/gemini/docs/codeassist/overview)
by signing up in the Google Cloud console. Learn more at
[Set up Gemini Code Assist](https://cloud.google.com/gemini/docs/discover/set-up-gemini).
### Through your organization
These tiers are applicable when you are signing in with a Google Workspace
account.
- To verify your account type, visit
[the Google One page](https://one.google.com/about/plans?hl=en-US&g1_landing_page=0).
- You are on a workspace account if you see the message "You're currently signed
in to your Google Workspace Account".
**Supported tiers:** _- Tiers not listed above, including Workspace AI
Standard/Plus and AI Expanded, are not supported._
- [Workspace AI Ultra Access](https://workspace.google.com/products/ai-ultra/).
- [Purchase a Gemini Code Assist Subscription through Google Cloud](https://cloud.google.com/gemini/docs/codeassist/overview).
Quotas and pricing are based on a fixed price subscription with assigned
license seats. For predictable costs, you can sign in with Google.
This includes:
This includes the following request limits:
- Gemini Code Assist Standard edition:
- 1500 model requests / user / day
- 120 model requests / user / minute
@@ -95,7 +120,7 @@ Gemini CLI by upgrading to one of the following subscriptions:
- Model requests will be made across the Gemini model family as determined by
Gemini CLI.
[Learn more about Gemini Code Assist Standard and Enterprise license limits](https://developers.google.com/gemini-code-assist/resources/quotas#quotas-for-agent-mode-gemini-cli).
[Learn more about Gemini Code Assist license limits](https://developers.google.com/gemini-code-assist/resources/quotas#quotas-for-agent-mode-gemini-cli).
## Pay as you go
@@ -106,18 +131,27 @@ recommended path for uninterrupted access.
To do this, log in using a Gemini API key or Vertex AI.
- Vertex AI (Regular Mode):
- Quota: Governed by a dynamic shared quota system or pre-purchased
provisioned throughput.
- Cost: Based on model and token usage.
### Vertex AI (regular mode)
An enterprise-grade platform for building, deploying, and managing AI models,
including Gemini. It offers enhanced security, data governance, and integration
with other Google Cloud services.
- Quota: Governed by a dynamic shared quota system or pre-purchased provisioned
throughput.
- Cost: Based on model and token usage.
Learn more at
[Vertex AI Dynamic Shared Quota](https://cloud.google.com/vertex-ai/generative-ai/docs/resources/dynamic-shared-quota)
and [Vertex AI Pricing](https://cloud.google.com/vertex-ai/pricing).
- Gemini API key:
- Quota: Varies by pricing tier.
- Cost: Varies by pricing tier and model/token usage.
### Gemini API key
Ideal for developers who want to quickly build applications with the Gemini
models. This is the most direct way to use the models.
- Quota: Varies by pricing tier.
- Cost: Varies by pricing tier and model/token usage.
Learn more at
[Gemini API Rate Limits](https://ai.google.dev/gemini-api/docs/rate-limits),
@@ -125,7 +159,8 @@ Learn more at
Its important to highlight that when using an API key, you pay per token/call.
This can be more expensive for many small calls with few tokens, but it's the
only way to ensure your workflow isn't interrupted by quota limits.
only way to ensure your workflow isn't interrupted by reaching a limit on your
quota.
## Gemini for workspace plans
@@ -135,31 +170,30 @@ Flow video editor). These plans do not apply to the API usage which powers the
Gemini CLI. Supporting these plans is under active consideration for future
support.
## Check usage and quota
## Check usage and limits
You can check your current token usage and quota information using the
You can check your current token usage and applicable limits using the
`/stats model` command. This command provides a snapshot of your current
session's token usage, as well as your overall quota and usage for the supported
models.
session's token usage, as well as information about the limits associated with
your current quota.
For more information on the `/stats` command and its subcommands, see the
[Command Reference](../../reference/commands.md#stats).
[Command Reference](../reference/commands.md#stats).
A summary of model usage is also presented on exit at the end of a session.
## Tips to avoid high costs
When using a Pay as you Go API key, be mindful of your usage to avoid unexpected
When using a pay-as-you-go plan, be mindful of your usage to avoid unexpected
costs.
- Don't blindly accept every suggestion, especially for computationally
intensive tasks like refactoring large codebases.
- Be intentional with your prompts and commands. You are paying per call, so
think about the most efficient way to get the job done.
## Gemini API vs. Vertex
- Gemini API (gemini developer api): This is the fastest way to use the Gemini
models directly.
- Vertex AI: This is the enterprise-grade platform for building, deploying, and
managing Gemini models with specific security and control requirements.
- **Be selective with suggestions**: Before accepting a suggestion, especially
for a computationally intensive task like refactoring a large codebase,
consider if it's the most cost-effective approach.
- **Use precise prompts**: You are paying per call, so think about the most
efficient way to get your desired result. A well-crafted prompt can often get
you the answer you need in a single call, rather than multiple back-and-forth
interactions.
- **Monitor your usage**: Use the `/stats model` command to track your token
usage during a session. This can help you stay aware of your spending in real
time.
+2 -2
View File
@@ -16,8 +16,8 @@ account.
Your Gemini CLI Usage Statistics are handled in accordance with Google's Privacy
Policy.
**Note:** See [quotas and pricing](/docs/resources/quota-and-pricing.md) for the
quota and pricing details that apply to your usage of the Gemini CLI.
**Note:** See [quotas and pricing](quota-and-pricing.md) for the quota and
pricing details that apply to your usage of the Gemini CLI.
## Supported authentication methods
+14 -2
View File
@@ -94,11 +94,23 @@
{ "label": "Agent Skills", "slug": "docs/cli/skills" },
{ "label": "Checkpointing", "slug": "docs/cli/checkpointing" },
{ "label": "Headless mode", "slug": "docs/cli/headless" },
{ "label": "Hooks", "slug": "docs/hooks" },
{
"label": "Hooks",
"collapsed": true,
"items": [
{ "label": "Overview", "slug": "docs/hooks" },
{ "label": "Reference", "slug": "docs/hooks/reference" }
]
},
{ "label": "IDE integration", "slug": "docs/ide-integration" },
{ "label": "MCP servers", "slug": "docs/tools/mcp-server" },
{ "label": "Model routing", "slug": "docs/cli/model-routing" },
{ "label": "Model selection", "slug": "docs/cli/model" },
{
"label": "Notifications",
"badge": "🔬",
"slug": "docs/cli/notifications"
},
{ "label": "Plan mode", "badge": "🔬", "slug": "docs/cli/plan-mode" },
{
"label": "Subagents",
@@ -181,7 +193,7 @@
"slug": "docs/reference/memport"
},
{ "label": "Policy engine", "slug": "docs/reference/policy-engine" },
{ "label": "Tools API", "slug": "docs/reference/tools-api" }
{ "label": "Tools reference", "slug": "docs/reference/tools" }
]
}
]
+2 -2
View File
@@ -67,7 +67,7 @@ Finds files matching specific glob patterns across the workspace.
`Found 5 file(s) matching "*.ts" within src, sorted by modification time (newest first):\nsrc/file1.ts\nsrc/subdir/file2.ts...`
- **Confirmation:** No.
## 5. `grep_search` (SearchText)
### `grep_search` (SearchText)
`grep_search` searches for a regular expression pattern within the content of
files in a specified directory. Can filter files by a glob pattern. Returns the
@@ -103,7 +103,7 @@ lines containing matches, along with their file paths and line numbers.
```
- **Confirmation:** No.
## 6. `replace` (Edit)
### `replace` (Edit)
`replace` replaces text within a file. By default, the tool expects to find and
replace exactly ONE occurrence of `old_string`. If you want to replace multiple
-105
View File
@@ -1,105 +0,0 @@
# Gemini CLI tools
Gemini CLI uses tools to interact with your local environment, access
information, and perform actions on your behalf. These tools extend the model's
capabilities beyond text generation, letting it read files, execute commands,
and search the web.
## User-triggered tools
You can directly trigger these tools using special syntax in your prompts.
- **[File access](./file-system.md#read_many_files) (`@`):** Use the `@` symbol
followed by a file or directory path to include its content in your prompt.
This triggers the `read_many_files` tool.
- **[Shell commands](./shell.md) (`!`):** Use the `!` symbol followed by a
system command to execute it directly. This triggers the `run_shell_command`
tool.
## Model-triggered tools
The Gemini model automatically requests these tools when it needs to perform
specific actions or gather information to fulfill your requests. You do not call
these tools manually.
### File management
These tools let the model explore and modify your local codebase.
- **[Directory listing](./file-system.md#list_directory) (`list_directory`):**
Lists files and subdirectories.
- **[File reading](./file-system.md#read_file) (`read_file`):** Reads the
content of a specific file.
- **[File writing](./file-system.md#write_file) (`write_file`):** Creates or
overwrites a file with new content.
- **[File search](./file-system.md#glob) (`glob`):** Finds files matching a glob
pattern.
- **[Text search](./file-system.md#search_file_content)
(`search_file_content`):** Searches for text within files using grep or
ripgrep.
- **[Text replacement](./file-system.md#replace) (`replace`):** Performs precise
edits within a file.
### Agent coordination
These tools help the model manage its plan and interact with you.
- **Ask user (`ask_user`):** Requests clarification or missing information from
you via an interactive dialog.
- **[Memory](./memory.md) (`save_memory`):** Saves important facts to your
long-term memory (`GEMINI.md`).
- **[Todos](./todos.md) (`write_todos`):** Manages a list of subtasks for
complex plans.
- **[Agent Skills](../cli/skills.md) (`activate_skill`):** Loads specialized
procedural expertise when needed.
- **[Browser agent](../core/subagents.md#browser-agent-experimental)
(`browser_agent`):** Automates web browser tasks through the accessibility
tree.
- **Internal docs (`get_internal_docs`):** Accesses Gemini CLI's own
documentation to help answer your questions.
### Information gathering
These tools provide the model with access to external data.
- **[Web fetch](./web-fetch.md) (`web_fetch`):** Retrieves and processes content
from specific URLs.
- **[Web search](./web-search.md) (`google_web_search`):** Performs a Google
Search to find up-to-date information.
## How to use tools
You use tools indirectly by providing natural language prompts to Gemini CLI.
1. **Prompt:** You enter a request or use syntax like `@` or `!`.
2. **Request:** The model analyzes your request and identifies if a tool is
required.
3. **Validation:** If a tool is needed, the CLI validates the parameters and
checks your security settings.
4. **Confirmation:** For sensitive operations (like writing files), the CLI
prompts you for approval.
5. **Execution:** The tool runs, and its output is sent back to the model.
6. **Response:** The model uses the results to generate a final, grounded
answer.
## Security and confirmation
Safety is a core part of the tool system. To protect your system, Gemini CLI
implements several safeguards.
- **User confirmation:** You must manually approve tools that modify files or
execute shell commands. The CLI shows you a diff or the exact command before
you confirm.
- **Sandboxing:** You can run tool executions in secure, containerized
environments to isolate changes from your host system. For more details, see
the [Sandboxing](../cli/sandbox.md) guide.
- **Trusted folders:** You can configure which directories allow the model to
use system tools.
Always review confirmation prompts carefully before allowing a tool to execute.
## Next steps
- Learn how to [Provide context](../cli/gemini-md.md) to guide tool use.
- Explore the [Command reference](../reference/commands.md) for tool-related
slash commands.
+1 -1
View File
@@ -372,7 +372,7 @@ To authenticate with a server using Service Account Impersonation, you must set
the `authProviderType` to `service_account_impersonation` and provide the
following properties:
- **`targetAudience`** (string): The OAuth Client ID allowslisted on the
- **`targetAudience`** (string): The OAuth Client ID allowlisted on the
IAP-protected application you are trying to access.
- **`targetServiceAccount`** (string): The email address of the Google Cloud
Service Account to impersonate.
+7 -6
View File
@@ -1,8 +1,8 @@
# Gemini CLI planning tools
Planning tools allow the Gemini model to switch into a safe, read-only "Plan
Mode" for researching and planning complex changes, and to signal the
finalization of a plan to the user.
Planning tools let Gemini CLI switch into a safe, read-only "Plan Mode" for
researching and planning complex changes, and to signal the finalization of a
plan to the user.
## 1. `enter_plan_mode` (EnterPlanMode)
@@ -18,11 +18,12 @@ and planning.
- **File:** `enter-plan-mode.ts`
- **Parameters:**
- `reason` (string, optional): A short reason explaining why the agent is
entering plan mode (e.g., "Starting a complex feature implementation").
entering plan mode (for example, "Starting a complex feature
implementation").
- **Behavior:**
- Switches the CLI's approval mode to `PLAN`.
- Notifies the user that the agent has entered Plan Mode.
- **Output (`llmContent`):** A message indicating the switch, e.g.,
- **Output (`llmContent`):** A message indicating the switch, for example,
`Switching to Plan mode.`
- **Confirmation:** Yes. The user is prompted to confirm entering Plan Mode.
@@ -37,7 +38,7 @@ finalized plan to the user and requests approval to start the implementation.
- **Parameters:**
- `plan_path` (string, required): The path to the finalized Markdown plan
file. This file MUST be located within the project's temporary plans
directory (e.g., `~/.gemini/tmp/<project>/plans/`).
directory (for example, `~/.gemini/tmp/<project>/plans/`).
- **Behavior:**
- Validates that the `plan_path` is within the allowed directory and that the
file exists and has content.
+16 -1
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(
@@ -132,7 +137,16 @@ export default tseslint.config(
'no-cond-assign': 'error',
'no-debugger': 'error',
'no-duplicate-case': 'error',
'no-restricted-syntax': ['error', ...commonRestrictedSyntaxRules],
'no-restricted-syntax': [
'error',
...commonRestrictedSyntaxRules,
{
selector:
'UnaryExpression[operator="typeof"] > MemberExpression[computed=true][property.type="Literal"]',
message:
'Do not use typeof to check object properties. Define a TypeScript interface and a type guard function instead.',
},
],
'no-unsafe-finally': 'error',
'no-unused-expressions': 'off', // Disable base rule
'@typescript-eslint/no-unused-expressions': [
@@ -263,6 +277,7 @@ export default tseslint.config(
...vitest.configs.recommended.rules,
'vitest/expect-expect': 'off',
'vitest/no-commented-out-tests': 'off',
'no-restricted-syntax': ['error', ...commonRestrictedSyntaxRules],
},
},
{
+56
View File
@@ -0,0 +1,56 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { expect } from 'vitest';
import { evalTest } from './test-helper.js';
const MUTATION_AGENT_DEFINITION = `---
name: mutation-agent
description: An agent that modifies the workspace (writes, deletes, git operations, etc).
max_turns: 1
tools:
- write_file
---
You are the mutation agent. Do the mutation requested.
`;
describe('concurrency safety eval test cases', () => {
evalTest('USUALLY_PASSES', {
name: 'mutation agents are run in parallel when explicitly requested',
params: {
settings: {
experimental: {
enableAgents: true,
},
},
},
prompt:
'Update A.txt to say "A" and update B.txt to say "B". Delegate these tasks to two separate mutation-agent subagents. You MUST run these subagents in parallel at the same time.',
files: {
'.gemini/agents/mutation-agent.md': MUTATION_AGENT_DEFINITION,
},
assert: async (rig) => {
const logs = rig.readToolLogs();
const mutationCalls = logs.filter(
(log) => log.toolRequest?.name === 'mutation-agent',
);
expect(
mutationCalls.length,
'Agent should have called the mutation-agent at least twice',
).toBeGreaterThanOrEqual(2);
const firstPromptId = mutationCalls[0].toolRequest.prompt_id;
const secondPromptId = mutationCalls[1].toolRequest.prompt_id;
expect(
firstPromptId,
'mutation agents should be called in parallel (same turn / prompt_ids) when explicitly requested',
).toEqual(secondPromptId);
},
});
});
+1
View File
@@ -112,6 +112,7 @@ export function evalTest(policy: EvalPolicy, evalCase: EvalCase) {
// commands.
execSync('git config core.editor "true"', execOptions);
execSync('git config core.pager "cat"', execOptions);
execSync('git config commit.gpgsign false', execOptions);
execSync('git add .', execOptions);
execSync('git commit --allow-empty -m "Initial commit"', execOptions);
}
+2 -2
View File
@@ -55,7 +55,7 @@ describe.skip('ACP Environment and Auth', () => {
const bundlePath = join(import.meta.dirname, '..', 'bundle/gemini.js');
child = spawn('node', [bundlePath, '--experimental-acp'], {
child = spawn('node', [bundlePath, '--acp'], {
cwd: rig.homeDir!,
stdio: ['pipe', 'pipe', 'inherit'],
env: {
@@ -120,7 +120,7 @@ describe.skip('ACP Environment and Auth', () => {
const bundlePath = join(import.meta.dirname, '..', 'bundle/gemini.js');
child = spawn('node', [bundlePath, '--experimental-acp'], {
child = spawn('node', [bundlePath, '--acp'], {
cwd: rig.homeDir!,
stdio: ['pipe', 'pipe', 'inherit'],
env: {
+1 -1
View File
@@ -58,7 +58,7 @@ describe('ACP telemetry', () => {
'node',
[
bundlePath,
'--experimental-acp',
'--acp',
'--fake-responses',
join(rig.testDir!, 'fake-responses.json'),
],
@@ -0,0 +1 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Part 1. "}],"role":"model"},"index":0}]},{"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":10,"totalTokenCount":110}},{"candidates":[{"content":{"parts":[{"text":"Part 2."}],"role":"model"},"index":0}],"finishReason":"STOP"}]}
+50
View File
@@ -0,0 +1,50 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { TestRig } from './test-helper.js';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
describe('API Resilience E2E', () => {
let rig: TestRig;
beforeEach(() => {
rig = new TestRig();
});
afterEach(async () => {
await rig.cleanup();
});
it('should not crash when receiving metadata-only chunks in a stream', async () => {
await rig.setup('api-resilience-metadata-only', {
fakeResponsesPath: join(
dirname(fileURLToPath(import.meta.url)),
'api-resilience.responses',
),
settings: {
planSettings: { modelRouting: false },
},
});
// Run the CLI with a simple prompt.
// The fake responses will provide a stream with a metadata-only chunk in the middle.
// We use gemini-3-pro-preview to minimize internal service calls.
const result = await rig.run({
args: ['hi', '--model', 'gemini-3-pro-preview'],
});
// Verify the output contains text from the normal chunks.
// If the CLI crashed on the metadata chunk, rig.run would throw.
expect(result).toContain('Part 1.');
expect(result).toContain('Part 2.');
// Verify telemetry event for the prompt was still generated
const hasUserPromptEvent = await rig.waitForTelemetryEvent('user_prompt');
expect(hasUserPromptEvent).toBe(true);
});
});
@@ -0,0 +1,2 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I'll open https://example.com and check the page title for you."},{"functionCall":{"name":"browser_agent","args":{"task":"Open https://example.com and get the page title"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":35,"totalTokenCount":135}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The page title of https://example.com is \"Example Domain\". The browser session has been completed and cleaned up successfully."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":30,"totalTokenCount":230}}]}
@@ -0,0 +1,2 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I'll navigate to https://example.com and analyze the links on the page."},{"functionCall":{"name":"browser_agent","args":{"task":"Go to https://example.com and find all links on the page, then describe them"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":40,"totalTokenCount":140}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"After analyzing https://example.com, I found the following links:\n\n1. **\"More information...\"** - This is the main link on the page that points to the IANA (Internet Assigned Numbers Authority) website for more details about reserved domains.\n\nThe page is quite minimal with just this single informational link, which is typical for example domains used in documentation."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":70,"totalTokenCount":270}}]}
@@ -0,0 +1,2 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I'll help you open https://example.com and analyze the page. Let me use the browser agent to navigate and capture the page information."},{"functionCall":{"name":"browser_agent","args":{"task":"Navigate to https://example.com and capture the accessibility tree to get the page title and main content"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":50,"totalTokenCount":150}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Based on the browser analysis of https://example.com:\n\n**Page Title**: Example Domain\n\n**Main Content**: The page contains a simple heading \"Example Domain\" and explanatory text stating \"This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.\"\n\nThe page has a clean, minimal layout typical of placeholder domains used for documentation and examples."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":80,"totalTokenCount":280}}]}
@@ -0,0 +1,2 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I'll navigate to https://example.com and take a screenshot for you."},{"functionCall":{"name":"browser_agent","args":{"task":"Navigate to https://example.com and take a screenshot of the page"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":40,"totalTokenCount":140}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I've successfully navigated to https://example.com and captured a screenshot. The page shows the familiar \"Example Domain\" header with explanatory text below it. The screenshot captures the clean, minimal layout of this demonstration website."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":50,"totalTokenCount":250}}]}
@@ -0,0 +1,2 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I'll help you navigate to https://example.com and perform both operations - capturing the accessibility tree and taking a screenshot."},{"functionCall":{"name":"browser_agent","args":{"task":"Navigate to https://example.com, take a snapshot of the accessibility tree, then take a screenshot"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":45,"totalTokenCount":145}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I've successfully completed both operations on https://example.com:\n\n**Accessibility Tree Snapshot**: The page has a clear structure with the main heading \"Example Domain\" and descriptive text about the domain's purpose for documentation examples.\n\n**Screenshot**: Captured a visual representation of the page showing the clean, minimal layout with the heading and explanatory text.\n\nBoth the accessibility data and visual screenshot confirm this is the standard example domain page used for documentation purposes."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":80,"totalTokenCount":280}}]}
+206
View File
@@ -0,0 +1,206 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* Integration tests for the browser agent.
*
* These tests verify the complete end-to-end flow from CLI prompt through
* browser_agent delegation to MCP/Chrome DevTools and back. Unlike the unit
* tests in packages/core/src/agents/browser/ which mock all MCP components,
* these tests launch real Chrome instances in headless mode.
*
* Tests are skipped on systems without Chrome/Chromium installed.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { TestRig, assertModelHasOutput } from './test-helper.js';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { execSync } from 'node:child_process';
import { existsSync } from 'node:fs';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const chromeAvailable = (() => {
try {
if (process.platform === 'darwin') {
execSync(
'test -d "/Applications/Google Chrome.app" || test -d "/Applications/Chromium.app"',
{
stdio: 'ignore',
},
);
} else if (process.platform === 'linux') {
execSync(
'which google-chrome || which chromium-browser || which chromium',
{ stdio: 'ignore' },
);
} else if (process.platform === 'win32') {
// Check standard Windows installation paths using Node.js fs
const chromePaths = [
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
`${process.env['LOCALAPPDATA'] ?? ''}\\Google\\Chrome\\Application\\chrome.exe`,
];
const found = chromePaths.some((p) => existsSync(p));
if (!found) {
// Fall back to PATH check
execSync('where chrome || where chromium', { stdio: 'ignore' });
}
} else {
return false;
}
return true;
} catch {
return false;
}
})();
describe.skipIf(!chromeAvailable)('browser-agent', () => {
let rig: TestRig;
beforeEach(() => {
rig = new TestRig();
});
afterEach(async () => await rig.cleanup());
it('should navigate to a page and capture accessibility tree', async () => {
rig.setup('browser-navigate-and-snapshot', {
fakeResponsesPath: join(
__dirname,
'browser-agent.navigate-snapshot.responses',
),
settings: {
agents: {
browser_agent: {
headless: true,
sessionMode: 'isolated',
},
},
},
});
const result = await rig.run({
args: 'Open https://example.com in the browser and tell me the page title and main content.',
});
assertModelHasOutput(result);
const toolLogs = rig.readToolLogs();
const browserAgentCall = toolLogs.find(
(t) => t.toolRequest.name === 'browser_agent',
);
expect(
browserAgentCall,
'Expected browser_agent to be called',
).toBeDefined();
});
it('should take screenshots of web pages', async () => {
rig.setup('browser-screenshot', {
fakeResponsesPath: join(__dirname, 'browser-agent.screenshot.responses'),
settings: {
agents: {
browser_agent: {
headless: true,
sessionMode: 'isolated',
},
},
},
});
const result = await rig.run({
args: 'Navigate to https://example.com and take a screenshot.',
});
const toolLogs = rig.readToolLogs();
const browserCalls = toolLogs.filter(
(t) => t.toolRequest.name === 'browser_agent',
);
expect(browserCalls.length).toBeGreaterThan(0);
assertModelHasOutput(result);
});
it('should interact with page elements', async () => {
rig.setup('browser-interaction', {
fakeResponsesPath: join(__dirname, 'browser-agent.interaction.responses'),
settings: {
agents: {
browser_agent: {
headless: true,
sessionMode: 'isolated',
},
},
},
});
const result = await rig.run({
args: 'Go to https://example.com, find any links on the page, and describe them.',
});
const toolLogs = rig.readToolLogs();
const browserAgentCall = toolLogs.find(
(t) => t.toolRequest.name === 'browser_agent',
);
expect(
browserAgentCall,
'Expected browser_agent to be called',
).toBeDefined();
assertModelHasOutput(result);
});
it('should clean up browser processes after completion', async () => {
rig.setup('browser-cleanup', {
fakeResponsesPath: join(__dirname, 'browser-agent.cleanup.responses'),
settings: {
agents: {
browser_agent: {
headless: true,
sessionMode: 'isolated',
},
},
},
});
await rig.run({
args: 'Open https://example.com in the browser and check the page title.',
});
// Test passes if we reach here, relying on Vitest's timeout mechanism
// to detect hanging browser processes.
});
it('should handle multiple browser operations in sequence', async () => {
rig.setup('browser-sequential', {
fakeResponsesPath: join(__dirname, 'browser-agent.sequential.responses'),
settings: {
agents: {
browser_agent: {
headless: true,
sessionMode: 'isolated',
},
},
},
});
const result = await rig.run({
args: 'Navigate to https://example.com, take a snapshot of the accessibility tree, then take a screenshot.',
});
const toolLogs = rig.readToolLogs();
const browserCalls = toolLogs.filter(
(t) => t.toolRequest.name === 'browser_agent',
);
expect(browserCalls.length).toBeGreaterThan(0);
// Should successfully complete all operations
assertModelHasOutput(result);
});
});
+2 -2
View File
@@ -104,7 +104,7 @@ describe('extension reloading', () => {
return (
output.includes(
'test-server (from test-extension) - Ready (1 tool)',
) && output.includes('- hello')
) && output.includes('- mcp_test-server_hello')
);
},
30000, // 30s timeout
@@ -148,7 +148,7 @@ describe('extension reloading', () => {
return (
output.includes(
'test-server (from test-extension) - Ready (1 tool)',
) && output.includes('- goodbye')
) && output.includes('- mcp_test-server_goodbye')
);
},
30000,
+19 -9
View File
@@ -182,14 +182,22 @@ describe('Hooks Agent Flow', () => {
);
const afterAgentScript = `
console.log(JSON.stringify({
decision: 'block',
reason: 'Security policy triggered',
hookSpecificOutput: {
hookEventName: 'AfterAgent',
clearContext: true
}
}));
const fs = require('fs');
const input = JSON.parse(fs.readFileSync(0, 'utf-8'));
if (input.stop_hook_active) {
// Retry turn: allow execution to proceed (breaks the loop)
console.log(JSON.stringify({ decision: 'allow' }));
} else {
// First call: block and clear context to trigger the retry
console.log(JSON.stringify({
decision: 'block',
reason: 'Security policy triggered',
hookSpecificOutput: {
hookEventName: 'AfterAgent',
clearContext: true
}
}));
}
`;
const afterAgentScriptPath = rig.createScript(
'after_agent_clear.cjs',
@@ -198,8 +206,10 @@ describe('Hooks Agent Flow', () => {
rig.setup('should process clearContext in AfterAgent hook output', {
settings: {
hooks: {
hooksConfig: {
enabled: true,
},
hooks: {
BeforeModel: [
{
hooks: [
@@ -1,2 +1,3 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Hi there!"}],"role":"model"},"finishReason":"STOP","index":0}]}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Clarification: I am a bot."}],"role":"model"},"finishReason":"STOP","index":0}]}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Security policy triggered"}],"role":"model"},"finishReason":"STOP","index":0}]}]}
@@ -0,0 +1,2 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I will read the content of the file to identify its"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":7969,"candidatesTokenCount":11,"totalTokenCount":8061,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7969}],"thoughtsTokenCount":81}},{"candidates":[{"content":{"parts":[{"text":" language.\n"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":7969,"candidatesTokenCount":14,"totalTokenCount":8064,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7969}],"thoughtsTokenCount":81}},{"candidates":[{"content":{"parts":[{"functionCall":{"name":"read_file","args":{"file_path":"test.txt"}},"thoughtSignature":"EvkCCvYCAb4+9vt8mJ/o45uuuAJtfjaZ3YzkJzqXHZBttRE+Om0ahcr1S5RDFp50KpgHtJtbAH1pwEXampOnDV3WKiWwA+e3Jnyk4CNQegz7ZMKsl55Nem2XDViP8BZKnJVqGmSFuMoKJLFmbVIxKejtWcblfn3httbGsrUUNbHwdPjPHo1qY043lF63g0kWx4v68gPSsJpNhxLrSugKKjiyRFN+J0rOIBHI2S9MdZoHEKhJxvGMtXiJquxmhPmKcNEsn+hMdXAZB39hmrRrGRHDQPVYVPhfJthVc73ufzbn+5KGJpaMQyKY5hqrc2ea8MHz+z6BSx+tFz4NZBff1tJQOiUp09/QndxQRZHSQZr1ALGy0O1Qw4JqsX94x81IxtXqYkSRo3zgm2vl/xPMC5lKlnK5xoKJmoWaHkUNeXs/sopu3/Waf1a5Csoh9ImnKQsW0rJ6GRyDQvky1FwR6Aa98bgfNdcXOPHml/BtghaqRMXTiG6vaPJ8UFs="}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":7969,"candidatesTokenCount":64,"totalTokenCount":8114,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7969}],"thoughtsTokenCount":81}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":7969,"candidatesTokenCount":64,"totalTokenCount":8114,"cachedContentTokenCount":6082,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7969}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":6082}],"thoughtsTokenCount":81}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The language of the file is Latin."}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":8054,"candidatesTokenCount":8,"totalTokenCount":8078,"promptTokensDetails":[{"modality":"TEXT","tokenCount":8054}],"thoughtsTokenCount":16}},{"candidates":[{"content":{"parts":[{"text":"","thoughtSignature":"EnIKcAG+Pvb7vnRBJVz3khx1oArQQqTNvXOXkliNQS7NvYw94dq5m+wGKRmSj3egO3GVp7pacnAtLn9NT1ABKBGpa7MpRhiAe3bbPZfkqOuveeyC19LKQ9fzasCywiYqg5k5qSxfjs5okk+O0NLOvTjN/tg="}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":8135,"candidatesTokenCount":8,"totalTokenCount":8159,"promptTokensDetails":[{"modality":"TEXT","tokenCount":8135}],"thoughtsTokenCount":16}}]}
@@ -0,0 +1,2 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I will run the requested"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":7949,"candidatesTokenCount":5,"totalTokenCount":8092,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7949}],"thoughtsTokenCount":138}},{"candidates":[{"content":{"parts":[{"text":" shell command to verify the policy configuration.\n"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":7949,"candidatesTokenCount":14,"totalTokenCount":8101,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7949}],"thoughtsTokenCount":138}},{"candidates":[{"content":{"parts":[{"functionCall":{"name":"run_shell_command","args":{"command":"echo POLICY_TEST_ECHO_COMMAND","description":"Echo the test string to verify policy settings."}},"thoughtSignature":"EpwFCpkFAb4+9vulXgVj96CAm2eMFbDEGHz9B37GwI8N1KOvu9AHwdYWiita7yS4RKAdeBui22B5320XBaxOtZGnMo2E9pG0Pcus2WsBiecRaHUTxTmhx1BvURevrs+5m4UJeLRGMfP94+ncha4DeIQod3PKBnK8xeIJTyZBFB7+hmHbHvem2VwZh/v14e4fXlpEkkdntJbzrA1nUdctIGdEmdm0sL8PaFnMqWLUnkZvGdfq7ctFt9EYk2HW2SrHVhk3HdsyWhoxNz2MU0sRWzAgiSQY/heSSAbU7Jdgg0RjwB9o3SkCIHxqnVpkH8PQsARwnah5I5s7pW6EHr3D4f1/UVl0n26hyI2xBqF/n4aZKhtX55U4h/DIhxooZa2znstt6BS8vRcdzflFrX7OV86WQxHE4JHjQecP2ciBRimm8pL3Od3pXnRcx32L8JbrWm6dPyWlo5h5uCRy0qXye2+3SuHs5wtxOjD9NETR4TwzqFe+m0zThpxsR1ZKQeKlO7lN/s3pWih/TjbZQEQs9xr72UnlE8ZtJ4bOKj8GNbemvsrbYAO98NzJwvdil0FhblaXmReP1uYjucmLC0jCJHShqNz2KzAkDTvKs4tmio13IuCRjTZ3E5owqCUn7djDqOSDwrg235RIVJkiDIaPlHemOR15lbVQD1VOzytzT8TZLEzTV750oyHq/IhLMQHYixO8jJ2GkVvUp7bxz9oQ4UeTqT5lTF4s40H2Rlkb6trF4hKXoFhzILy1aOJTC9W3fCoop7VJLIMNulgHLWxiq65Uas6sIep87yiD4xLfbGfMm6HS4JTRhPlfxeckn/SzUfu1afg1nAvW3vBlR/YNREf0N28/PnRC08VYqA3mqCRiyPqPWsf3a0jyio0dD9A="}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":7949,"candidatesTokenCount":54,"totalTokenCount":8141,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7949}],"thoughtsTokenCount":138}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":7949,"candidatesTokenCount":54,"totalTokenCount":8141,"cachedContentTokenCount":6082,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7949}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":6082}],"thoughtsTokenCount":138}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"POLICY_TEST_"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":8042,"candidatesTokenCount":4,"totalTokenCount":8046,"promptTokensDetails":[{"modality":"TEXT","tokenCount":8042}]}},{"candidates":[{"content":{"parts":[{"text":"ECHO_COMMAND"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":8042,"candidatesTokenCount":8,"totalTokenCount":8050,"promptTokensDetails":[{"modality":"TEXT","tokenCount":8042}]}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":8180,"candidatesTokenCount":8,"totalTokenCount":8188,"promptTokensDetails":[{"modality":"TEXT","tokenCount":8180}]}}]}
@@ -0,0 +1,2 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"**Assessing Command Execution**\n\nOkay, I'm currently assessing the feasibility of executing `echo POLICY_TEST_ECHO_COMMAND` using the `run_shell_command` function. Restrictions are being evaluated; the prompt is specifically geared towards a successful command output: \"POLICY_TEST_ECHO_COMMAND\".\n\n\n","thought":true}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":7949,"totalTokenCount":7949,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7949}]}},{"candidates":[{"content":{"parts":[{"text":"I will execute the requested echo"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":7949,"candidatesTokenCount":6,"totalTokenCount":8161,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7949}],"thoughtsTokenCount":206}},{"candidates":[{"content":{"parts":[{"text":" command to verify the policy."}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":7949,"candidatesTokenCount":12,"totalTokenCount":8167,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7949}],"thoughtsTokenCount":206}},{"candidates":[{"content":{"parts":[{"functionCall":{"name":"run_shell_command","args":{"description":"Execute the echo command as requested.","command":"echo POLICY_TEST_ECHO_COMMAND"}},"thoughtSignature":"EvkGCvYGAb4+9vucYbmJ8DrNCca9c0C8o4qKQ6V2WnzmT4mbCw8V7s0+2I/PoxrgnsxZJIIRM8y5E4bW7Jbs46GjbJ2cefY9Q3iC45eiGS5Gqvq0eAG04N3GZRwizyDOp+wJlBsaPu1cNB1t6CnMk/ZHDAHEIQUpYfYWmPudbHOQMspGMu3bX23YSI1+Q5vPVdOtM16J3EFbk3dCp+RnPa/8tVC+5AqFlLveuDbJXtrLN9wAyf4SjnPhn9BPfD0bgas3+gF03qRJvWoNcnnJiYxL3DNQtjsAYJ7IWRzciYYZSTm99blD730bn3NzvSObhlHDtb3hFpApYvG396+3prsgJg0Yjef54B4KxHfZaQbE2ndSP5zGrwLtVD5y7XJAYskvhiUqwPFHNVykqroEMzPn8wWQSGvonNR6ezcMIsUV5xwnxZDaPhvrDdIwF4NR1F5DeriJRu27+fwtCApeYkx9mPx4LqnyxOuVsILjzdSPHE6Bqf690VJSXpo67lCN4F3DRRYIuCD4UOlf8V3dvUO6BKjvChDDWnIq7KPoByDQT9VhVlZvS3/nYlkeDuhi0rk2jpByN1NdgD2YSvOlpJcka8JqKQ+lnO/7Swunij2ISUfpL2hkx6TEHjebPU2dBQkub5nSl9J1EhZn4sUGG5r6Zdv1lYcpIcO4ZYeMqZZ4uNvTvSpGdT4Jj1+qS88taKgYq7uN1RgQSTsT5wcpmlubIpgIycNwAIRFvN+DjkQjiUC6hSqdeOx3dc7LWgC/O/+PRog7kuFrD2nzih+oIP0YxXrLA9CMVPlzeAgPUi9b75HAJQ92GRHxfQ163tjZY+4bWmJtcU4NBqGH0x/jLEU9xCojTeh+mZoUDGsb3N+bVcGJftRIet7IBYveD29Z+XHtKhf7s/YIkFW8lgsG8Q0EtNchCxqIQxf9UjYEO52RhCx7i7zScB1knovt2HAotACKqDdPqg18PmpDv8Frw6Y66XeCCJzBCmNcSUTETq3K05gwkU8nyANQtjbJT0wF4LS9h5vPE+Vc7/dGH6pi1TgxWB/n4q1IXfNqilo/h2Pyw01VPsHKthNtKKq1/nSW/WuEU0rimqu7wHplMqU2nwRDCTNE9pPO59RtTHMfUxxd8yEgKBj9L8MiQGM5isIYl/lJtvucee4HD9iLpbYADlrQAlUCd0rg/z+5sQ=="}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":7949,"candidatesTokenCount":50,"totalTokenCount":8205,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7949}],"thoughtsTokenCount":206}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":7949,"candidatesTokenCount":50,"totalTokenCount":8205,"cachedContentTokenCount":6082,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7949}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":6082}],"thoughtsTokenCount":206}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"AR NAR"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":8020,"candidatesTokenCount":2,"totalTokenCount":8049,"promptTokensDetails":[{"modality":"TEXT","tokenCount":8020}],"thoughtsTokenCount":27}},{"candidates":[{"content":{"parts":[{"text":"","thoughtSignature":"Er8BCrwBAb4+9vv6KGeMf6yopmPBE/az7Kjdp+Pe5a/R6wgXcyCZzGNwkwKFW3i3ro0j26bRrVeHD1zRfWFTIGdOSZKV6OMPWLqFC/RU6CNJ88B1xY7hbCVwA7EchYPzgd3YZRVNwmFu52j86/9qXf/zaqTFN+WQ0mUESJXh2O2YX8E7imAvxhmRdobVkxvEt4ZX3dW5skDhXHMDZOxbLpX0nkK7cWWS7iEc+qBFP0yinlA/eiG2ZdKpuTiDl76a9ik="}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":8226,"candidatesTokenCount":2,"totalTokenCount":8255,"promptTokensDetails":[{"modality":"TEXT","tokenCount":8226}],"thoughtsTokenCount":27}}]}
+205
View File
@@ -0,0 +1,205 @@
/**
* @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 } from './test-helper.js';
interface PromptCommand {
prompt: (testFile: string) => string;
tool: string;
command: string;
expectedSuccessResult: string;
expectedFailureResult: string;
}
const ECHO_PROMPT: PromptCommand = {
command: 'echo',
prompt: () =>
`Use the \`echo POLICY_TEST_ECHO_COMMAND\` shell command. On success, ` +
`your final response must ONLY be "POLICY_TEST_ECHO_COMMAND". If the ` +
`command fails output AR NAR and stop.`,
tool: 'run_shell_command',
expectedSuccessResult: 'POLICY_TEST_ECHO_COMMAND',
expectedFailureResult: 'AR NAR',
};
const READ_FILE_PROMPT: PromptCommand = {
prompt: (testFile: string) =>
`Read the file ${testFile} and tell me what language it is, if the ` +
`read_file tool fails output AR NAR and stop.`,
tool: 'read_file',
command: '',
expectedSuccessResult: 'Latin',
expectedFailureResult: 'AR NAR',
};
async function waitForToolCallLog(
rig: TestRig,
tool: string,
command: string,
timeout: number = 15000,
) {
const foundToolCall = await rig.waitForToolCall(tool, timeout, (args) =>
args.toLowerCase().includes(command.toLowerCase()),
);
expect(foundToolCall).toBe(true);
const toolLogs = rig
.readToolLogs()
.filter((toolLog) => toolLog.toolRequest.name === tool);
const log = toolLogs.find(
(toolLog) =>
!command ||
toolLog.toolRequest.args.toLowerCase().includes(command.toLowerCase()),
);
// The policy engine should have logged the tool call
expect(log).toBeTruthy();
return log;
}
async function verifyToolExecution(
rig: TestRig,
promptCommand: PromptCommand,
result: string,
expectAllowed: boolean,
expectedDenialString?: string,
) {
const log = await waitForToolCallLog(
rig,
promptCommand.tool,
promptCommand.command,
);
if (expectAllowed) {
expect(log!.toolRequest.success).toBe(true);
expect(result).not.toContain('Tool execution denied by policy');
expect(result).not.toContain(`Tool "${promptCommand.tool}" not found`);
expect(result).toContain(promptCommand.expectedSuccessResult);
} else {
expect(log!.toolRequest.success).toBe(false);
expect(result).toContain(
expectedDenialString || 'Tool execution denied by policy',
);
expect(result).toContain(promptCommand.expectedFailureResult);
}
}
interface TestCase {
name: string;
responsesFile: string;
promptCommand: PromptCommand;
policyContent?: string;
expectAllowed: boolean;
expectedDenialString?: string;
}
describe('Policy Engine Headless Mode', () => {
let rig: TestRig;
let testFile: string;
beforeEach(() => {
rig = new TestRig();
});
afterEach(async () => {
if (rig) {
await rig.cleanup();
}
});
const runTestCase = async (tc: TestCase) => {
const fakeResponsesPath = join(import.meta.dirname, tc.responsesFile);
rig.setup(tc.name, { fakeResponsesPath });
testFile = rig.createFile('test.txt', 'Lorem\nIpsum\nDolor\n');
const args = ['-p', tc.promptCommand.prompt(testFile)];
if (tc.policyContent) {
const policyPath = rig.createFile('test-policy.toml', tc.policyContent);
args.push('--policy', policyPath);
}
const result = await rig.run({
args,
approvalMode: 'default',
});
await verifyToolExecution(
rig,
tc.promptCommand,
result,
tc.expectAllowed,
tc.expectedDenialString,
);
};
const testCases = [
{
name: 'should deny ASK_USER tools by default in headless mode',
responsesFile: 'policy-headless-shell-denied.responses',
promptCommand: ECHO_PROMPT,
expectAllowed: false,
expectedDenialString: 'Tool "run_shell_command" not found',
},
{
name: 'should allow ASK_USER tools in headless mode if explicitly allowed via policy file',
responsesFile: 'policy-headless-shell-allowed.responses',
promptCommand: ECHO_PROMPT,
policyContent: `
[[rule]]
toolName = "run_shell_command"
decision = "allow"
priority = 100
`,
expectAllowed: true,
},
{
name: 'should allow read-only tools by default in headless mode',
responsesFile: 'policy-headless-readonly.responses',
promptCommand: READ_FILE_PROMPT,
expectAllowed: true,
},
{
name: 'should allow specific shell commands in policy file',
responsesFile: 'policy-headless-shell-allowed.responses',
promptCommand: ECHO_PROMPT,
policyContent: `
[[rule]]
toolName = "run_shell_command"
commandPrefix = "${ECHO_PROMPT.command}"
decision = "allow"
priority = 100
`,
expectAllowed: true,
},
{
name: 'should deny other shell commands in policy file',
responsesFile: 'policy-headless-shell-denied.responses',
promptCommand: ECHO_PROMPT,
policyContent: `
[[rule]]
toolName = "run_shell_command"
commandPrefix = "node"
decision = "allow"
priority = 100
`,
expectAllowed: false,
expectedDenialString: 'Tool execution denied by policy',
},
];
it.each(testCases)(
'$name',
async (tc) => {
await runTestCase(tc);
},
// Large timeout for regeneration
process.env['REGENERATE_MODEL_GOLDENS'] === 'true' ? 120000 : undefined,
);
});
+287 -79
View File
@@ -1,12 +1,12 @@
{
"name": "@google/gemini-cli",
"version": "0.33.0-nightly.20260228.1ca5c05d0",
"version": "0.34.0-nightly.20260304.28af4e127",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@google/gemini-cli",
"version": "0.33.0-nightly.20260228.1ca5c05d0",
"version": "0.34.0-nightly.20260304.28af4e127",
"workspaces": [
"packages/*"
],
@@ -84,9 +84,9 @@
}
},
"node_modules/@a2a-js/sdk": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/@a2a-js/sdk/-/sdk-0.3.8.tgz",
"integrity": "sha512-vAg6JQbhOnHTzApsB7nGzCQ9r7PuY4GMr8gt88dIR8Wc8G8RSqVTyTmFeMurgzcYrtHYXS3ru2rnDoGj9UDeSw==",
"version": "0.3.10",
"resolved": "https://registry.npmjs.org/@a2a-js/sdk/-/sdk-0.3.10.tgz",
"integrity": "sha512-t6w5ctnwJkSOMRl6M9rn95C1FTHCPqixxMR0yWXtzhZXEnF6mF1NAK0CfKlG3cz+tcwTxkmn287QZC3t9XPgrA==",
"license": "Apache-2.0",
"dependencies": {
"uuid": "^11.1.0"
@@ -95,9 +95,17 @@
"node": ">=18"
},
"peerDependencies": {
"@bufbuild/protobuf": "^2.10.2",
"@grpc/grpc-js": "^1.11.0",
"express": "^4.21.2 || ^5.1.0"
},
"peerDependenciesMeta": {
"@bufbuild/protobuf": {
"optional": true
},
"@grpc/grpc-js": {
"optional": true
},
"express": {
"optional": true
}
@@ -515,6 +523,12 @@
"node": ">=18"
}
},
"node_modules/@bufbuild/protobuf": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.11.0.tgz",
"integrity": "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==",
"license": "(Apache-2.0 AND BSD-3-Clause)"
},
"node_modules/@bundled-es-modules/cookie": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz",
@@ -1582,18 +1596,36 @@
}
},
"node_modules/@grpc/grpc-js": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.4.tgz",
"integrity": "sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==",
"version": "1.14.3",
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz",
"integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==",
"license": "Apache-2.0",
"dependencies": {
"@grpc/proto-loader": "^0.7.13",
"@grpc/proto-loader": "^0.8.0",
"@js-sdsl/ordered-map": "^4.4.2"
},
"engines": {
"node": ">=12.10.0"
}
},
"node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz",
"integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==",
"license": "Apache-2.0",
"dependencies": {
"lodash.camelcase": "^4.3.0",
"long": "^5.0.0",
"protobufjs": "^7.5.3",
"yargs": "^17.7.2"
},
"bin": {
"proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@grpc/proto-loader": {
"version": "0.7.15",
"resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz",
@@ -5464,13 +5496,6 @@
"node": ">=8"
}
},
"node_modules/array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT",
"peer": true
},
"node_modules/array-includes": {
"version": "3.1.9",
"resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz",
@@ -6305,16 +6330,36 @@
"node": ">= 12"
}
},
"node_modules/clipboardy": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-5.0.0.tgz",
"integrity": "sha512-MQfKHaD09eP80Pev4qBxZLbxJK/ONnqfSYAPlCmPh+7BDboYtO/3BmB6HGzxDIT0SlTRc2tzS8lQqfcdLtZ0Kg==",
"node_modules/clipboard-image": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/clipboard-image/-/clipboard-image-0.1.0.tgz",
"integrity": "sha512-SWk7FgaXLNFld19peQ/rTe0n97lwR1WbkqxV6JKCAOh7U52AKV/PeMFCyt/8IhBdqyDA8rdyewQMKZqvWT5Akg==",
"license": "MIT",
"dependencies": {
"execa": "^9.6.0",
"run-jxa": "^3.0.0"
},
"bin": {
"clipboard-image": "cli.js"
},
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/clipboardy": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-5.2.1.tgz",
"integrity": "sha512-RWp4E/ivQAzgF4QSWA9sjeW+Bjo+U2SvebkDhNIfO7y65eGdXPUxMTdIKYsn+bxM3ItPHGm3e68Bv3fgQ3mARw==",
"license": "MIT",
"dependencies": {
"clipboard-image": "^0.1.0",
"execa": "^9.6.1",
"is-wayland": "^0.1.0",
"is-wsl": "^3.1.0",
"is64bit": "^2.0.0"
"is64bit": "^2.0.0",
"powershell-utils": "^0.2.0"
},
"engines": {
"node": ">=20"
@@ -6570,7 +6615,6 @@
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
"integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"safe-buffer": "5.2.1"
},
@@ -6740,6 +6784,33 @@
"node": ">= 8"
}
},
"node_modules/crypto-random-string": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz",
"integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==",
"license": "MIT",
"dependencies": {
"type-fest": "^1.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/crypto-random-string/node_modules/type-fest": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz",
"integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==",
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/css-select": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
@@ -8430,9 +8501,9 @@
}
},
"node_modules/execa": {
"version": "9.6.0",
"resolved": "https://registry.npmjs.org/execa/-/execa-9.6.0.tgz",
"integrity": "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==",
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz",
"integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==",
"license": "MIT",
"dependencies": {
"@sindresorhus/merge-streams": "^4.0.0",
@@ -8550,36 +8621,15 @@
"express": ">= 4.11"
}
},
"node_modules/express/node_modules/cookie": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"peer": true,
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/express/node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.8"
}
},
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@@ -8839,7 +8889,6 @@
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"peer": true,
"dependencies": {
"ms": "2.0.0"
}
@@ -8848,18 +8897,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT",
"peer": true
},
"node_modules/finalhandler/node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.8"
}
"license": "MIT"
},
"node_modules/find-up": {
"version": "5.0.0",
@@ -11735,6 +11773,21 @@
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"license": "ISC"
},
"node_modules/macos-version": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/macos-version/-/macos-version-6.0.0.tgz",
"integrity": "sha512-O2S8voA+pMfCHhBn/TIYDXzJ1qNHpPDU32oFxglKnVdJABiYYITt45oLkV9yhwA3E2FDwn3tQqUFrTsr1p3sBQ==",
"license": "MIT",
"dependencies": {
"semver": "^7.3.5"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
@@ -11870,6 +11923,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
"license": "MIT"
},
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -13420,6 +13479,18 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/powershell-utils": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.2.0.tgz",
"integrity": "sha512-ZlsFlG7MtSFCoc5xreOvBAozCJ6Pf06opgJjh9ONEv418xpZSAzNjstD36C6+JwOnfSqOW/9uDkqKjezTdxZhw==",
"license": "MIT",
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -14302,6 +14373,107 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/run-jxa": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/run-jxa/-/run-jxa-3.0.0.tgz",
"integrity": "sha512-4f2CrY7H+sXkKXJn/cE6qRA3z+NMVO7zvlZ/nUV0e62yWftpiLAfw5eV9ZdomzWd2TXWwEIiGjAT57+lWIzzvA==",
"license": "MIT",
"dependencies": {
"execa": "^5.1.1",
"macos-version": "^6.0.0",
"subsume": "^4.0.0",
"type-fest": "^2.0.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/run-jxa/node_modules/execa": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
"integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
"license": "MIT",
"dependencies": {
"cross-spawn": "^7.0.3",
"get-stream": "^6.0.0",
"human-signals": "^2.1.0",
"is-stream": "^2.0.0",
"merge-stream": "^2.0.0",
"npm-run-path": "^4.0.1",
"onetime": "^5.1.2",
"signal-exit": "^3.0.3",
"strip-final-newline": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sindresorhus/execa?sponsor=1"
}
},
"node_modules/run-jxa/node_modules/get-stream": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
"integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/run-jxa/node_modules/human-signals": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
"integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
"license": "Apache-2.0",
"engines": {
"node": ">=10.17.0"
}
},
"node_modules/run-jxa/node_modules/npm-run-path": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
"integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
"license": "MIT",
"dependencies": {
"path-key": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/run-jxa/node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"license": "ISC"
},
"node_modules/run-jxa/node_modules/strip-final-newline": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
"integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/run-jxa/node_modules/type-fest": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
"integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=12.20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -15238,6 +15410,34 @@
"integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==",
"license": "MIT"
},
"node_modules/subsume": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/subsume/-/subsume-4.0.0.tgz",
"integrity": "sha512-BWnYJElmHbYZ/zKevy+TG+SsyoFCmRPDHJbR1MzLxkPOv1Jp/4hGhVUtP98s+wZBsBsHwCXvPTP0x287/WMjGg==",
"license": "MIT",
"dependencies": {
"escape-string-regexp": "^5.0.0",
"unique-string": "^3.0.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/subsume/node_modules/escape-string-regexp": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
"integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/superagent": {
"version": "10.2.3",
"resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.3.tgz",
@@ -16220,6 +16420,21 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/unique-string": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz",
"integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==",
"license": "MIT",
"dependencies": {
"crypto-random-string": "^4.0.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/universal-user-agent": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz",
@@ -16286,16 +16501,6 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
@@ -17130,7 +17335,7 @@
},
"packages/a2a-server": {
"name": "@google/gemini-cli-a2a-server",
"version": "0.33.0-nightly.20260228.1ca5c05d0",
"version": "0.34.0-nightly.20260304.28af4e127",
"dependencies": {
"@a2a-js/sdk": "^0.3.8",
"@google-cloud/storage": "^7.16.0",
@@ -17188,7 +17393,7 @@
},
"packages/cli": {
"name": "@google/gemini-cli",
"version": "0.33.0-nightly.20260228.1ca5c05d0",
"version": "0.34.0-nightly.20260304.28af4e127",
"license": "Apache-2.0",
"dependencies": {
"@agentclientprotocol/sdk": "^0.12.0",
@@ -17200,7 +17405,7 @@
"ansi-regex": "^6.2.2",
"chalk": "^4.1.2",
"cli-spinners": "^2.9.2",
"clipboardy": "^5.0.0",
"clipboardy": "~5.2.0",
"color-convert": "^2.0.1",
"command-exists": "^1.2.9",
"comment-json": "^4.2.5",
@@ -17271,14 +17476,16 @@
},
"packages/core": {
"name": "@google/gemini-cli-core",
"version": "0.33.0-nightly.20260228.1ca5c05d0",
"version": "0.34.0-nightly.20260304.28af4e127",
"license": "Apache-2.0",
"dependencies": {
"@a2a-js/sdk": "^0.3.8",
"@a2a-js/sdk": "^0.3.10",
"@bufbuild/protobuf": "^2.11.0",
"@google-cloud/logging": "^11.2.1",
"@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.21.0",
"@google-cloud/opentelemetry-cloud-trace-exporter": "^3.0.0",
"@google/genai": "1.41.0",
"@grpc/grpc-js": "^1.14.3",
"@iarna/toml": "^2.2.5",
"@joshua.litt/get-ripgrep": "^0.0.3",
"@modelcontextprotocol/sdk": "^1.23.0",
@@ -17316,6 +17523,7 @@
"html-to-text": "^9.0.5",
"https-proxy-agent": "^7.0.6",
"ignore": "^7.0.0",
"ipaddr.js": "^1.9.1",
"js-yaml": "^4.1.1",
"marked": "^15.0.12",
"mime": "4.0.7",
@@ -17536,7 +17744,7 @@
},
"packages/devtools": {
"name": "@google/gemini-cli-devtools",
"version": "0.33.0-nightly.20260228.1ca5c05d0",
"version": "0.34.0-nightly.20260304.28af4e127",
"license": "Apache-2.0",
"dependencies": {
"ws": "^8.16.0"
@@ -17551,7 +17759,7 @@
},
"packages/sdk": {
"name": "@google/gemini-cli-sdk",
"version": "0.33.0-nightly.20260228.1ca5c05d0",
"version": "0.34.0-nightly.20260304.28af4e127",
"license": "Apache-2.0",
"dependencies": {
"@google/gemini-cli-core": "file:../core",
@@ -17568,7 +17776,7 @@
},
"packages/test-utils": {
"name": "@google/gemini-cli-test-utils",
"version": "0.33.0-nightly.20260228.1ca5c05d0",
"version": "0.34.0-nightly.20260304.28af4e127",
"license": "Apache-2.0",
"dependencies": {
"@google/gemini-cli-core": "file:../core",
@@ -17585,7 +17793,7 @@
},
"packages/vscode-ide-companion": {
"name": "gemini-cli-vscode-ide-companion",
"version": "0.33.0-nightly.20260228.1ca5c05d0",
"version": "0.34.0-nightly.20260304.28af4e127",
"license": "LICENSE",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.23.0",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli",
"version": "0.33.0-nightly.20260228.1ca5c05d0",
"version": "0.34.0-nightly.20260304.28af4e127",
"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.33.0-nightly.20260228.1ca5c05d0"
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.34.0-nightly.20260304.28af4e127"
},
"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.33.0-nightly.20260228.1ca5c05d0",
"version": "0.34.0-nightly.20260304.28af4e127",
"description": "Gemini CLI A2A Server",
"repository": {
"type": "git",
+13 -3
View File
@@ -28,6 +28,9 @@ import {
type Config,
type UserTierId,
type ToolLiveOutput,
type AnsiLine,
type AnsiOutput,
type AnsiToken,
isSubagentProgress,
EDIT_TOOL_NAMES,
processRestorableToolCalls,
@@ -344,10 +347,15 @@ export class Task {
outputAsText = outputChunk;
} else if (isSubagentProgress(outputChunk)) {
outputAsText = JSON.stringify(outputChunk);
} else {
outputAsText = outputChunk
.map((line) => line.map((token) => token.text).join(''))
} else if (Array.isArray(outputChunk)) {
const ansiOutput: AnsiOutput = outputChunk;
outputAsText = ansiOutput
.map((line: AnsiLine) =>
line.map((token: AnsiToken) => token.text).join(''),
)
.join('\n');
} else {
outputAsText = String(outputChunk);
}
logger.info(
@@ -824,7 +832,9 @@ export class Task {
if (
part.kind !== 'data' ||
!part.data ||
// eslint-disable-next-line no-restricted-syntax
typeof part.data['callId'] !== 'string' ||
// eslint-disable-next-line no-restricted-syntax
typeof part.data['outcome'] !== 'string'
) {
return false;
+225 -7
View File
@@ -16,6 +16,9 @@ import {
ExperimentFlags,
fetchAdminControlsOnce,
type FetchAdminControlsResponse,
AuthType,
isHeadlessMode,
FatalAuthenticationError,
} from '@google/gemini-cli-core';
// Mock dependencies
@@ -50,6 +53,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
startupProfiler: {
flush: vi.fn(),
},
isHeadlessMode: vi.fn().mockReturnValue(false),
FileDiscoveryService: vi.fn(),
getCodeAssistServer: vi.fn(),
fetchAdminControlsOnce: vi.fn(),
@@ -62,6 +66,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
vi.mock('../utils/logger.js', () => ({
logger: {
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
},
}));
@@ -73,12 +78,11 @@ describe('loadConfig', () => {
beforeEach(() => {
vi.clearAllMocks();
process.env['GEMINI_API_KEY'] = 'test-key';
vi.stubEnv('GEMINI_API_KEY', 'test-key');
});
afterEach(() => {
delete process.env['CUSTOM_IGNORE_FILE_PATHS'];
delete process.env['GEMINI_API_KEY'];
vi.unstubAllEnvs();
});
describe('admin settings overrides', () => {
@@ -199,7 +203,7 @@ describe('loadConfig', () => {
it('should set customIgnoreFilePaths when CUSTOM_IGNORE_FILE_PATHS env var is present', async () => {
const testPath = '/tmp/ignore';
process.env['CUSTOM_IGNORE_FILE_PATHS'] = testPath;
vi.stubEnv('CUSTOM_IGNORE_FILE_PATHS', testPath);
const config = await loadConfig(mockSettings, mockExtensionLoader, taskId);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
expect((config as any).fileFiltering.customIgnoreFilePaths).toEqual([
@@ -224,7 +228,7 @@ describe('loadConfig', () => {
it('should merge customIgnoreFilePaths from settings and env var', async () => {
const envPath = '/env/ignore';
const settingsPath = '/settings/ignore';
process.env['CUSTOM_IGNORE_FILE_PATHS'] = envPath;
vi.stubEnv('CUSTOM_IGNORE_FILE_PATHS', envPath);
const settings: Settings = {
fileFiltering: {
customIgnoreFilePaths: [settingsPath],
@@ -240,7 +244,7 @@ describe('loadConfig', () => {
it('should split CUSTOM_IGNORE_FILE_PATHS using system delimiter', async () => {
const paths = ['/path/one', '/path/two'];
process.env['CUSTOM_IGNORE_FILE_PATHS'] = paths.join(path.delimiter);
vi.stubEnv('CUSTOM_IGNORE_FILE_PATHS', paths.join(path.delimiter));
const config = await loadConfig(mockSettings, mockExtensionLoader, taskId);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
expect((config as any).fileFiltering.customIgnoreFilePaths).toEqual(paths);
@@ -254,7 +258,7 @@ describe('loadConfig', () => {
it('should initialize FileDiscoveryService with correct options', async () => {
const testPath = '/tmp/ignore';
process.env['CUSTOM_IGNORE_FILE_PATHS'] = testPath;
vi.stubEnv('CUSTOM_IGNORE_FILE_PATHS', testPath);
const settings: Settings = {
fileFiltering: {
respectGitIgnore: false,
@@ -311,5 +315,219 @@ describe('loadConfig', () => {
}),
);
});
describe('interactivity', () => {
it('should set interactive true when not headless', async () => {
vi.mocked(isHeadlessMode).mockReturnValue(false);
await loadConfig(mockSettings, mockExtensionLoader, taskId);
expect(Config).toHaveBeenCalledWith(
expect.objectContaining({
interactive: true,
enableInteractiveShell: true,
}),
);
});
it('should set interactive false when headless', async () => {
vi.mocked(isHeadlessMode).mockReturnValue(true);
await loadConfig(mockSettings, mockExtensionLoader, taskId);
expect(Config).toHaveBeenCalledWith(
expect.objectContaining({
interactive: false,
enableInteractiveShell: false,
}),
);
});
});
describe('authentication fallback', () => {
beforeEach(() => {
vi.stubEnv('USE_CCPA', 'true');
vi.stubEnv('GEMINI_API_KEY', '');
});
afterEach(() => {
vi.unstubAllEnvs();
});
it('should fall back to COMPUTE_ADC in Cloud Shell if LOGIN_WITH_GOOGLE fails', async () => {
vi.stubEnv('CLOUD_SHELL', 'true');
vi.mocked(isHeadlessMode).mockReturnValue(false);
const refreshAuthMock = vi.fn().mockImplementation((authType) => {
if (authType === AuthType.LOGIN_WITH_GOOGLE) {
throw new FatalAuthenticationError('Non-interactive session');
}
return Promise.resolve();
});
// Update the mock implementation for this test
vi.mocked(Config).mockImplementation(
(params: unknown) =>
({
...(params as object),
initialize: vi.fn(),
waitForMcpInit: vi.fn(),
refreshAuth: refreshAuthMock,
getExperiments: vi.fn().mockReturnValue({ flags: {} }),
getRemoteAdminSettings: vi.fn(),
setRemoteAdminSettings: vi.fn(),
}) as unknown as Config,
);
await loadConfig(mockSettings, mockExtensionLoader, taskId);
expect(refreshAuthMock).toHaveBeenCalledWith(
AuthType.LOGIN_WITH_GOOGLE,
);
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.COMPUTE_ADC);
});
it('should not fall back to COMPUTE_ADC if not in cloud environment', async () => {
vi.mocked(isHeadlessMode).mockReturnValue(false);
const refreshAuthMock = vi.fn().mockImplementation((authType) => {
if (authType === AuthType.LOGIN_WITH_GOOGLE) {
throw new FatalAuthenticationError('Non-interactive session');
}
return Promise.resolve();
});
vi.mocked(Config).mockImplementation(
(params: unknown) =>
({
...(params as object),
initialize: vi.fn(),
waitForMcpInit: vi.fn(),
refreshAuth: refreshAuthMock,
getExperiments: vi.fn().mockReturnValue({ flags: {} }),
getRemoteAdminSettings: vi.fn(),
setRemoteAdminSettings: vi.fn(),
}) as unknown as Config,
);
await expect(
loadConfig(mockSettings, mockExtensionLoader, taskId),
).rejects.toThrow('Non-interactive session');
expect(refreshAuthMock).toHaveBeenCalledWith(
AuthType.LOGIN_WITH_GOOGLE,
);
expect(refreshAuthMock).not.toHaveBeenCalledWith(AuthType.COMPUTE_ADC);
});
it('should skip LOGIN_WITH_GOOGLE and use COMPUTE_ADC directly in headless Cloud Shell', async () => {
vi.stubEnv('CLOUD_SHELL', 'true');
vi.mocked(isHeadlessMode).mockReturnValue(true);
const refreshAuthMock = vi.fn().mockResolvedValue(undefined);
vi.mocked(Config).mockImplementation(
(params: unknown) =>
({
...(params as object),
initialize: vi.fn(),
waitForMcpInit: vi.fn(),
refreshAuth: refreshAuthMock,
getExperiments: vi.fn().mockReturnValue({ flags: {} }),
getRemoteAdminSettings: vi.fn(),
setRemoteAdminSettings: vi.fn(),
}) as unknown as Config,
);
await loadConfig(mockSettings, mockExtensionLoader, taskId);
expect(refreshAuthMock).not.toHaveBeenCalledWith(
AuthType.LOGIN_WITH_GOOGLE,
);
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.COMPUTE_ADC);
});
it('should skip LOGIN_WITH_GOOGLE and use COMPUTE_ADC directly if GEMINI_CLI_USE_COMPUTE_ADC is true', async () => {
vi.stubEnv('GEMINI_CLI_USE_COMPUTE_ADC', 'true');
vi.mocked(isHeadlessMode).mockReturnValue(false); // Even if not headless
const refreshAuthMock = vi.fn().mockResolvedValue(undefined);
vi.mocked(Config).mockImplementation(
(params: unknown) =>
({
...(params as object),
initialize: vi.fn(),
waitForMcpInit: vi.fn(),
refreshAuth: refreshAuthMock,
getExperiments: vi.fn().mockReturnValue({ flags: {} }),
getRemoteAdminSettings: vi.fn(),
setRemoteAdminSettings: vi.fn(),
}) as unknown as Config,
);
await loadConfig(mockSettings, mockExtensionLoader, taskId);
expect(refreshAuthMock).not.toHaveBeenCalledWith(
AuthType.LOGIN_WITH_GOOGLE,
);
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.COMPUTE_ADC);
});
it('should throw FatalAuthenticationError in headless mode if no ADC fallback available', async () => {
vi.mocked(isHeadlessMode).mockReturnValue(true);
const refreshAuthMock = vi.fn().mockResolvedValue(undefined);
vi.mocked(Config).mockImplementation(
(params: unknown) =>
({
...(params as object),
initialize: vi.fn(),
waitForMcpInit: vi.fn(),
refreshAuth: refreshAuthMock,
getExperiments: vi.fn().mockReturnValue({ flags: {} }),
getRemoteAdminSettings: vi.fn(),
setRemoteAdminSettings: vi.fn(),
}) as unknown as Config,
);
await expect(
loadConfig(mockSettings, mockExtensionLoader, taskId),
).rejects.toThrow(
'Interactive terminal required for LOGIN_WITH_GOOGLE. Run in an interactive terminal or set GEMINI_CLI_USE_COMPUTE_ADC=true to use Application Default Credentials.',
);
expect(refreshAuthMock).not.toHaveBeenCalled();
});
it('should include both original and fallback error when COMPUTE_ADC fallback fails', async () => {
vi.stubEnv('CLOUD_SHELL', 'true');
vi.mocked(isHeadlessMode).mockReturnValue(false);
const refreshAuthMock = vi.fn().mockImplementation((authType) => {
if (authType === AuthType.LOGIN_WITH_GOOGLE) {
throw new FatalAuthenticationError('OAuth failed');
}
if (authType === AuthType.COMPUTE_ADC) {
throw new Error('ADC failed');
}
return Promise.resolve();
});
vi.mocked(Config).mockImplementation(
(params: unknown) =>
({
...(params as object),
initialize: vi.fn(),
waitForMcpInit: vi.fn(),
refreshAuth: refreshAuthMock,
getExperiments: vi.fn().mockReturnValue({ flags: {} }),
getRemoteAdminSettings: vi.fn(),
setRemoteAdminSettings: vi.fn(),
}) as unknown as Config,
);
await expect(
loadConfig(mockSettings, mockExtensionLoader, taskId),
).rejects.toThrow(
'OAuth failed. Fallback to COMPUTE_ADC also failed: ADC failed',
);
});
});
});
});
+60 -4
View File
@@ -23,6 +23,9 @@ import {
fetchAdminControlsOnce,
getCodeAssistServer,
ExperimentFlags,
isHeadlessMode,
FatalAuthenticationError,
isCloudShell,
type TelemetryTarget,
type ConfigParameters,
type ExtensionLoader,
@@ -103,8 +106,8 @@ export async function loadConfig(
trustedFolder: true,
extensionLoader,
checkpointing,
interactive: true,
enableInteractiveShell: true,
interactive: !isHeadlessMode(),
enableInteractiveShell: !isHeadlessMode(),
ptyInfo: 'auto',
};
@@ -117,7 +120,6 @@ export async function loadConfig(
await loadServerHierarchicalMemory(
workspaceDir,
[workspaceDir],
false,
fileService,
extensionLoader,
folderTrust,
@@ -255,7 +257,61 @@ async function refreshAuthentication(
`[${logPrefix}] USE_CCPA env var is true but unable to resolve GOOGLE_APPLICATION_CREDENTIALS file path ${adcFilePath}. Error ${e}`,
);
}
await config.refreshAuth(AuthType.LOGIN_WITH_GOOGLE);
const useComputeAdc = process.env['GEMINI_CLI_USE_COMPUTE_ADC'] === 'true';
const isHeadless = isHeadlessMode();
const shouldSkipOauth = isHeadless || useComputeAdc;
if (shouldSkipOauth) {
if (isCloudShell() || useComputeAdc) {
logger.info(
`[${logPrefix}] Skipping LOGIN_WITH_GOOGLE due to ${isHeadless ? 'headless mode' : 'GEMINI_CLI_USE_COMPUTE_ADC'}. Attempting COMPUTE_ADC.`,
);
try {
await config.refreshAuth(AuthType.COMPUTE_ADC);
logger.info(`[${logPrefix}] COMPUTE_ADC successful.`);
} catch (adcError) {
const adcMessage =
adcError instanceof Error ? adcError.message : String(adcError);
throw new FatalAuthenticationError(
`COMPUTE_ADC failed: ${adcMessage}. (Skipped LOGIN_WITH_GOOGLE due to ${isHeadless ? 'headless mode' : 'GEMINI_CLI_USE_COMPUTE_ADC'})`,
);
}
} else {
throw new FatalAuthenticationError(
`Interactive terminal required for LOGIN_WITH_GOOGLE. Run in an interactive terminal or set GEMINI_CLI_USE_COMPUTE_ADC=true to use Application Default Credentials.`,
);
}
} else {
try {
await config.refreshAuth(AuthType.LOGIN_WITH_GOOGLE);
} catch (e) {
if (
e instanceof FatalAuthenticationError &&
(isCloudShell() || useComputeAdc)
) {
logger.warn(
`[${logPrefix}] LOGIN_WITH_GOOGLE failed. Attempting COMPUTE_ADC fallback.`,
);
try {
await config.refreshAuth(AuthType.COMPUTE_ADC);
logger.info(`[${logPrefix}] COMPUTE_ADC fallback successful.`);
} catch (adcError) {
logger.error(
`[${logPrefix}] COMPUTE_ADC fallback failed: ${adcError}`,
);
const originalMessage = e instanceof Error ? e.message : String(e);
const adcMessage =
adcError instanceof Error ? adcError.message : String(adcError);
throw new FatalAuthenticationError(
`${originalMessage}. Fallback to COMPUTE_ADC also failed: ${adcMessage}`,
);
}
} else {
throw e;
}
}
}
logger.info(
`[${logPrefix}] GOOGLE_CLOUD_PROJECT: ${process.env['GOOGLE_CLOUD_PROJECT']}`,
);
@@ -75,6 +75,14 @@ export function createMockConfig(
validatePathAccess: vi.fn().mockReturnValue(undefined),
...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';
mockConfig.getMessageBus = vi.fn().mockReturnValue(createMockMessageBus());
mockConfig.getHookSystem = vi
.fn()
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli",
"version": "0.33.0-nightly.20260228.1ca5c05d0",
"version": "0.34.0-nightly.20260304.28af4e127",
"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.33.0-nightly.20260228.1ca5c05d0"
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.34.0-nightly.20260304.28af4e127"
},
"dependencies": {
"@agentclientprotocol/sdk": "^0.12.0",
@@ -38,7 +38,7 @@
"ansi-regex": "^6.2.2",
"chalk": "^4.1.2",
"cli-spinners": "^2.9.2",
"clipboardy": "^5.0.0",
"clipboardy": "~5.2.0",
"color-convert": "^2.0.1",
"command-exists": "^1.2.9",
"comment-json": "^4.2.5",
@@ -14,7 +14,8 @@ import {
type Mock,
type Mocked,
} from 'vitest';
import { GeminiAgent, Session } from './zedIntegration.js';
import { GeminiAgent, Session } from './acpClient.js';
import type { CommandHandler } from './commandHandler.js';
import * as acp from '@agentclientprotocol/sdk';
import {
AuthType,
@@ -26,6 +27,7 @@ import {
type Config,
type MessageBus,
LlmRole,
type GitService,
} from '@google/gemini-cli-core';
import {
SettingScope,
@@ -62,7 +64,33 @@ vi.mock('node:path', async (importOriginal) => {
};
});
// Mock ReadManyFilesTool
vi.mock('../ui/commands/memoryCommand.js', () => ({
memoryCommand: {
name: 'memory',
action: vi.fn(),
},
}));
vi.mock('../ui/commands/extensionsCommand.js', () => ({
extensionsCommand: vi.fn().mockReturnValue({
name: 'extensions',
action: vi.fn(),
}),
}));
vi.mock('../ui/commands/restoreCommand.js', () => ({
restoreCommand: vi.fn().mockReturnValue({
name: 'restore',
action: vi.fn(),
}),
}));
vi.mock('../ui/commands/initCommand.js', () => ({
initCommand: {
name: 'init',
action: vi.fn(),
},
}));
vi.mock(
'@google/gemini-cli-core',
async (
@@ -144,7 +172,10 @@ describe('GeminiAgent', () => {
unsubscribe: vi.fn(),
}),
getApprovalMode: vi.fn().mockReturnValue('default'),
isPlanEnabled: vi.fn().mockReturnValue(false),
isPlanEnabled: vi.fn().mockReturnValue(true),
getGemini31LaunchedSync: vi.fn().mockReturnValue(false),
getHasAccessToPreviewModel: vi.fn().mockReturnValue(false),
getCheckpointingEnabled: vi.fn().mockReturnValue(false),
} as unknown as Mocked<Awaited<ReturnType<typeof loadCliConfig>>>;
mockSettings = {
merged: {
@@ -177,7 +208,16 @@ describe('GeminiAgent', () => {
});
expect(response.protocolVersion).toBe(acp.PROTOCOL_VERSION);
expect(response.authMethods).toHaveLength(3);
expect(response.authMethods).toHaveLength(4);
const gatewayAuth = response.authMethods?.find(
(m) => m.id === AuthType.GATEWAY,
);
expect(gatewayAuth?._meta).toEqual({
gateway: {
protocol: 'google',
restartRequired: 'false',
},
});
const geminiAuth = response.authMethods?.find(
(m) => m.id === AuthType.USE_GEMINI,
);
@@ -197,6 +237,8 @@ describe('GeminiAgent', () => {
expect(mockConfig.refreshAuth).toHaveBeenCalledWith(
AuthType.LOGIN_WITH_GOOGLE,
undefined,
undefined,
undefined,
);
expect(mockSettings.setValue).toHaveBeenCalledWith(
SettingScope.User,
@@ -216,6 +258,8 @@ describe('GeminiAgent', () => {
expect(mockConfig.refreshAuth).toHaveBeenCalledWith(
AuthType.USE_GEMINI,
'test-api-key',
undefined,
undefined,
);
expect(mockSettings.setValue).toHaveBeenCalledWith(
SettingScope.User,
@@ -224,7 +268,47 @@ describe('GeminiAgent', () => {
);
});
it('should authenticate correctly with gateway method', async () => {
await agent.authenticate({
methodId: AuthType.GATEWAY,
_meta: {
gateway: {
baseUrl: 'https://example.com',
headers: { Authorization: 'Bearer token' },
},
},
} as unknown as acp.AuthenticateRequest);
expect(mockConfig.refreshAuth).toHaveBeenCalledWith(
AuthType.GATEWAY,
undefined,
'https://example.com',
{ Authorization: 'Bearer token' },
);
expect(mockSettings.setValue).toHaveBeenCalledWith(
SettingScope.User,
'security.auth.selectedType',
AuthType.GATEWAY,
);
});
it('should throw acp.RequestError when gateway payload is malformed', async () => {
await expect(
agent.authenticate({
methodId: AuthType.GATEWAY,
_meta: {
gateway: {
// Invalid baseUrl
baseUrl: 123,
headers: { Authorization: 'Bearer token' },
},
},
} as unknown as acp.AuthenticateRequest),
).rejects.toThrow(/Malformed gateway payload/);
});
it('should create a new session', async () => {
vi.useFakeTimers();
mockConfig.getContentGeneratorConfig = vi.fn().mockReturnValue({
apiKey: 'test-key',
});
@@ -237,6 +321,17 @@ describe('GeminiAgent', () => {
expect(loadCliConfig).toHaveBeenCalled();
expect(mockConfig.initialize).toHaveBeenCalled();
expect(mockConfig.getGeminiClient).toHaveBeenCalled();
// Verify deferred call
await vi.runAllTimersAsync();
expect(mockConnection.sessionUpdate).toHaveBeenCalledWith(
expect.objectContaining({
update: expect.objectContaining({
sessionUpdate: 'available_commands_update',
}),
}),
);
vi.useRealTimers();
});
it('should return modes without plan mode when plan is disabled', async () => {
@@ -263,6 +358,38 @@ describe('GeminiAgent', () => {
],
currentModeId: 'default',
});
expect(response.models).toEqual({
availableModels: expect.arrayContaining([
expect.objectContaining({
modelId: 'auto-gemini-2.5',
name: 'Auto (Gemini 2.5)',
}),
]),
currentModelId: 'gemini-pro',
});
});
it('should include preview models when user has access', async () => {
mockConfig.getHasAccessToPreviewModel = vi.fn().mockReturnValue(true);
mockConfig.getGemini31LaunchedSync = vi.fn().mockReturnValue(true);
const response = await agent.newSession({
cwd: '/tmp',
mcpServers: [],
});
expect(response.models?.availableModels).toEqual(
expect.arrayContaining([
expect.objectContaining({
modelId: 'auto-gemini-3',
name: expect.stringContaining('Auto'),
}),
expect.objectContaining({
modelId: 'gemini-3.1-pro-preview',
name: 'gemini-3.1-pro-preview',
}),
]),
);
});
it('should return modes with plan mode when plan is enabled', async () => {
@@ -290,6 +417,15 @@ describe('GeminiAgent', () => {
],
currentModeId: 'plan',
});
expect(response.models).toEqual({
availableModels: expect.arrayContaining([
expect.objectContaining({
modelId: 'auto-gemini-2.5',
name: 'Auto (Gemini 2.5)',
}),
]),
currentModelId: 'gemini-pro',
});
});
it('should fail session creation if Gemini API key is missing', async () => {
@@ -439,6 +575,32 @@ describe('GeminiAgent', () => {
}),
).rejects.toThrow('Session not found: unknown');
});
it('should delegate setModel to session (unstable)', async () => {
await agent.newSession({ cwd: '/tmp', mcpServers: [] });
const session = (
agent as unknown as { sessions: Map<string, Session> }
).sessions.get('test-session-id');
if (!session) throw new Error('Session not found');
session.setModel = vi.fn().mockReturnValue({});
const result = await agent.unstable_setSessionModel({
sessionId: 'test-session-id',
modelId: 'gemini-2.0-pro-exp',
});
expect(session.setModel).toHaveBeenCalledWith('gemini-2.0-pro-exp');
expect(result).toEqual({});
});
it('should throw error when setting model on non-existent session (unstable)', async () => {
await expect(
agent.unstable_setSessionModel({
sessionId: 'unknown',
modelId: 'gemini-2.0-pro-exp',
}),
).rejects.toThrow('Session not found: unknown');
});
});
describe('Session', () => {
@@ -477,6 +639,7 @@ describe('Session', () => {
getModel: vi.fn().mockReturnValue('gemini-pro'),
getActiveModel: vi.fn().mockReturnValue('gemini-pro'),
getToolRegistry: vi.fn().mockReturnValue(mockToolRegistry),
getMcpServers: vi.fn(),
getFileService: vi.fn().mockReturnValue({
shouldIgnoreFile: vi.fn().mockReturnValue(false),
}),
@@ -486,7 +649,10 @@ describe('Session', () => {
getDebugMode: vi.fn().mockReturnValue(false),
getMessageBus: vi.fn().mockReturnValue(mockMessageBus),
setApprovalMode: vi.fn(),
isPlanEnabled: vi.fn().mockReturnValue(false),
setModel: vi.fn(),
isPlanEnabled: vi.fn().mockReturnValue(true),
getCheckpointingEnabled: vi.fn().mockReturnValue(false),
getGitService: vi.fn().mockResolvedValue({} as GitService),
waitForMcpInit: vi.fn(),
} as unknown as Mocked<Config>;
mockConnection = {
@@ -495,13 +661,38 @@ describe('Session', () => {
sendNotification: vi.fn(),
} as unknown as Mocked<acp.AgentSideConnection>;
session = new Session('session-1', mockChat, mockConfig, mockConnection);
session = new Session('session-1', mockChat, mockConfig, mockConnection, {
system: { settings: {} },
systemDefaults: { settings: {} },
user: { settings: {} },
workspace: { settings: {} },
merged: { settings: {} },
errors: [],
} as unknown as LoadedSettings);
});
afterEach(() => {
vi.clearAllMocks();
});
it('should send available commands', async () => {
await session.sendAvailableCommands();
expect(mockConnection.sessionUpdate).toHaveBeenCalledWith(
expect.objectContaining({
update: expect.objectContaining({
sessionUpdate: 'available_commands_update',
availableCommands: expect.arrayContaining([
expect.objectContaining({ name: 'memory' }),
expect.objectContaining({ name: 'extensions' }),
expect.objectContaining({ name: 'restore' }),
expect.objectContaining({ name: 'init' }),
]),
}),
}),
);
});
it('should await MCP initialization before processing a prompt', async () => {
const stream = createMockStream([
{
@@ -551,6 +742,113 @@ describe('Session', () => {
expect(result).toEqual({ stopReason: 'end_turn' });
});
it('should handle /memory command', async () => {
const handleCommandSpy = vi
.spyOn(
(session as unknown as { commandHandler: CommandHandler })
.commandHandler,
'handleCommand',
)
.mockResolvedValue(true);
const result = await session.prompt({
sessionId: 'session-1',
prompt: [{ type: 'text', text: '/memory view' }],
});
expect(result).toEqual({ stopReason: 'end_turn' });
expect(handleCommandSpy).toHaveBeenCalledWith(
'/memory view',
expect.any(Object),
);
expect(mockChat.sendMessageStream).not.toHaveBeenCalled();
});
it('should handle /extensions command', async () => {
const handleCommandSpy = vi
.spyOn(
(session as unknown as { commandHandler: CommandHandler })
.commandHandler,
'handleCommand',
)
.mockResolvedValue(true);
const result = await session.prompt({
sessionId: 'session-1',
prompt: [{ type: 'text', text: '/extensions list' }],
});
expect(result).toEqual({ stopReason: 'end_turn' });
expect(handleCommandSpy).toHaveBeenCalledWith(
'/extensions list',
expect.any(Object),
);
expect(mockChat.sendMessageStream).not.toHaveBeenCalled();
});
it('should handle /extensions explore command', async () => {
const handleCommandSpy = vi
.spyOn(
(session as unknown as { commandHandler: CommandHandler })
.commandHandler,
'handleCommand',
)
.mockResolvedValue(true);
const result = await session.prompt({
sessionId: 'session-1',
prompt: [{ type: 'text', text: '/extensions explore' }],
});
expect(result).toEqual({ stopReason: 'end_turn' });
expect(handleCommandSpy).toHaveBeenCalledWith(
'/extensions explore',
expect.any(Object),
);
expect(mockChat.sendMessageStream).not.toHaveBeenCalled();
});
it('should handle /restore command', async () => {
const handleCommandSpy = vi
.spyOn(
(session as unknown as { commandHandler: CommandHandler })
.commandHandler,
'handleCommand',
)
.mockResolvedValue(true);
const result = await session.prompt({
sessionId: 'session-1',
prompt: [{ type: 'text', text: '/restore' }],
});
expect(result).toEqual({ stopReason: 'end_turn' });
expect(handleCommandSpy).toHaveBeenCalledWith(
'/restore',
expect.any(Object),
);
expect(mockChat.sendMessageStream).not.toHaveBeenCalled();
});
it('should handle /init command', async () => {
const handleCommandSpy = vi
.spyOn(
(session as unknown as { commandHandler: CommandHandler })
.commandHandler,
'handleCommand',
)
.mockResolvedValue(true);
const result = await session.prompt({
sessionId: 'session-1',
prompt: [{ type: 'text', text: '/init' }],
});
expect(result).toEqual({ stopReason: 'end_turn' });
expect(handleCommandSpy).toHaveBeenCalledWith('/init', expect.any(Object));
expect(mockChat.sendMessageStream).not.toHaveBeenCalled();
});
it('should handle tool calls', async () => {
const stream1 = createMockStream([
{
@@ -1207,4 +1505,30 @@ describe('Session', () => {
'Invalid or unavailable mode: invalid-mode',
);
});
it('should set model on config', () => {
session.setModel('gemini-2.0-flash-exp');
expect(mockConfig.setModel).toHaveBeenCalledWith('gemini-2.0-flash-exp');
});
it('should handle unquoted commands from autocomplete (with empty leading parts)', async () => {
// Mock handleCommand to verify it gets called
const handleCommandSpy = vi
.spyOn(
(session as unknown as { commandHandler: CommandHandler })
.commandHandler,
'handleCommand',
)
.mockResolvedValue(true);
await session.prompt({
sessionId: 'session-1',
prompt: [
{ type: 'text', text: '' },
{ type: 'text', text: '/memory' },
],
});
expect(handleCommandSpy).toHaveBeenCalledWith('/memory', expect.anything());
});
});
@@ -4,15 +4,13 @@
* SPDX-License-Identifier: Apache-2.0
*/
import type {
Config,
GeminiChat,
ToolResult,
ToolCallConfirmationDetails,
FilterFilesOptions,
ConversationRecord,
} from '@google/gemini-cli-core';
import {
type Config,
type GeminiChat,
type ToolResult,
type ToolCallConfirmationDetails,
type FilterFilesOptions,
type ConversationRecord,
CoreToolCallStatus,
AuthType,
logToolCall,
@@ -39,6 +37,16 @@ import {
ApprovalMode,
getVersion,
convertSessionToClientHistory,
DEFAULT_GEMINI_MODEL,
DEFAULT_GEMINI_FLASH_MODEL,
DEFAULT_GEMINI_FLASH_LITE_MODEL,
PREVIEW_GEMINI_MODEL,
PREVIEW_GEMINI_3_1_MODEL,
PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL,
PREVIEW_GEMINI_FLASH_MODEL,
DEFAULT_GEMINI_MODEL_AUTO,
PREVIEW_GEMINI_MODEL_AUTO,
getDisplayString,
} from '@google/gemini-cli-core';
import * as acp from '@agentclientprotocol/sdk';
import { AcpFileSystemService } from './fileSystemService.js';
@@ -61,11 +69,14 @@ import { loadCliConfig } from '../config/config.js';
import { runExitCleanup } from '../utils/cleanup.js';
import { SessionSelector } from '../utils/sessionUtils.js';
export async function runZedIntegration(
import { CommandHandler } from './commandHandler.js';
export async function runAcpClient(
config: Config,
settings: LoadedSettings,
argv: CliArgs,
) {
// ... (skip unchanged lines) ...
const { stdout: workingStdout } = createWorkingStdio();
const stdout = Writable.toWeb(workingStdout) as WritableStream;
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
@@ -87,6 +98,8 @@ export class GeminiAgent {
private sessions: Map<string, Session> = new Map();
private clientCapabilities: acp.ClientCapabilities | undefined;
private apiKey: string | undefined;
private baseUrl: string | undefined;
private customHeaders: Record<string, string> | undefined;
constructor(
private config: Config,
@@ -120,6 +133,17 @@ export class GeminiAgent {
name: 'Vertex AI',
description: 'Use an API key with Vertex AI GenAI API',
},
{
id: AuthType.GATEWAY,
name: 'AI API Gateway',
description: 'Use a custom AI API Gateway',
_meta: {
gateway: {
protocol: 'google',
restartRequired: 'false',
},
},
},
];
await this.config.initialize();
@@ -168,7 +192,38 @@ export class GeminiAgent {
if (apiKey) {
this.apiKey = apiKey;
}
await this.config.refreshAuth(method, apiKey ?? this.apiKey);
// Extract gateway details if present
const gatewaySchema = z.object({
baseUrl: z.string().optional(),
headers: z.record(z.string()).optional(),
});
let baseUrl: string | undefined;
let headers: Record<string, string> | undefined;
if (meta?.['gateway']) {
const result = gatewaySchema.safeParse(meta['gateway']);
if (result.success) {
baseUrl = result.data.baseUrl;
headers = result.data.headers;
} else {
throw new acp.RequestError(
-32602,
`Malformed gateway payload: ${result.error.message}`,
);
}
}
this.baseUrl = baseUrl;
this.customHeaders = headers;
await this.config.refreshAuth(
method,
apiKey ?? this.apiKey,
baseUrl,
headers,
);
} catch (e) {
throw new acp.RequestError(-32000, getAcpErrorMessage(e));
}
@@ -198,7 +253,12 @@ export class GeminiAgent {
let isAuthenticated = false;
let authErrorMessage = '';
try {
await config.refreshAuth(authType, this.apiKey);
await config.refreshAuth(
authType,
this.apiKey,
this.baseUrl,
this.customHeaders,
);
isAuthenticated = true;
// Extra validation for Gemini API key
@@ -240,16 +300,37 @@ export class GeminiAgent {
const geminiClient = config.getGeminiClient();
const chat = await geminiClient.startChat();
const session = new Session(sessionId, chat, config, this.connection);
const session = new Session(
sessionId,
chat,
config,
this.connection,
this.settings,
);
this.sessions.set(sessionId, session);
return {
setTimeout(() => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
session.sendAvailableCommands();
}, 0);
const { availableModels, currentModelId } = buildAvailableModels(
config,
loadedSettings,
);
const response = {
sessionId,
modes: {
availableModes: buildAvailableModes(config.isPlanEnabled()),
currentModeId: config.getApprovalMode(),
},
models: {
availableModels,
currentModelId,
},
};
return response;
}
async loadSession({
@@ -291,6 +372,7 @@ export class GeminiAgent {
geminiClient.getChat(),
config,
this.connection,
this.settings,
);
this.sessions.set(sessionId, session);
@@ -298,12 +380,27 @@ export class GeminiAgent {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
session.streamHistory(sessionData.messages);
return {
setTimeout(() => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
session.sendAvailableCommands();
}, 0);
const { availableModels, currentModelId } = buildAvailableModels(
config,
this.settings,
);
const response = {
modes: {
availableModes: buildAvailableModes(config.isPlanEnabled()),
currentModeId: config.getApprovalMode(),
},
models: {
availableModels,
currentModelId,
},
};
return response;
}
private async initializeSessionConfig(
@@ -323,7 +420,12 @@ export class GeminiAgent {
// This satisfies the security requirement to verify the user before executing
// potentially unsafe server definitions.
try {
await config.refreshAuth(selectedAuthType, this.apiKey);
await config.refreshAuth(
selectedAuthType,
this.apiKey,
this.baseUrl,
this.customHeaders,
);
} catch (e) {
debugLogger.error(`Authentication failed: ${e}`);
throw acp.RequestError.authRequired();
@@ -414,16 +516,28 @@ export class GeminiAgent {
}
return session.setMode(params.modeId);
}
async unstable_setSessionModel(
params: acp.SetSessionModelRequest,
): Promise<acp.SetSessionModelResponse> {
const session = this.sessions.get(params.sessionId);
if (!session) {
throw new Error(`Session not found: ${params.sessionId}`);
}
return session.setModel(params.modelId);
}
}
export class Session {
private pendingPrompt: AbortController | null = null;
private commandHandler = new CommandHandler();
constructor(
private readonly id: string,
private readonly chat: GeminiChat,
private readonly config: Config,
private readonly connection: acp.AgentSideConnection,
private readonly settings: LoadedSettings,
) {}
async cancelPendingPrompt(): Promise<void> {
@@ -446,6 +560,27 @@ export class Session {
return {};
}
private getAvailableCommands() {
return this.commandHandler.getAvailableCommands();
}
async sendAvailableCommands(): Promise<void> {
const availableCommands = this.getAvailableCommands().map((command) => ({
name: command.name,
description: command.description,
}));
await this.sendUpdate({
sessionUpdate: 'available_commands_update',
availableCommands,
});
}
setModel(modelId: acp.ModelId): acp.SetSessionModelResponse {
this.config.setModel(modelId);
return {};
}
async streamHistory(messages: ConversationRecord['messages']): Promise<void> {
for (const msg of messages) {
const contentString = partListUnionToString(msg.content);
@@ -528,6 +663,41 @@ export class Session {
const parts = await this.#resolvePrompt(params.prompt, pendingSend.signal);
// Command interception
let commandText = '';
for (const part of parts) {
if (typeof part === 'object' && part !== null) {
if ('text' in part) {
// It is a text part
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-type-assertion
const text = (part as any).text;
if (typeof text === 'string') {
commandText += text;
}
} else {
// Non-text part (image, embedded resource)
// Stop looking for command
break;
}
}
}
commandText = commandText.trim();
if (
commandText &&
(commandText.startsWith('/') || commandText.startsWith('$'))
) {
// If we found a command, pass it to handleCommand
// Note: handleCommand currently expects `commandText` to be the command string
// It uses `parts` argument but effectively ignores it in current implementation
const handled = await this.handleCommand(commandText, parts);
if (handled) {
return { stopReason: 'end_turn' };
}
}
let nextMessage: Content | null = { role: 'user', parts };
while (nextMessage !== null) {
@@ -627,9 +797,28 @@ export class Session {
return { stopReason: 'end_turn' };
}
private async sendUpdate(
update: acp.SessionNotification['update'],
): Promise<void> {
private async handleCommand(
commandText: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
parts: Part[],
): Promise<boolean> {
const gitService = await this.config.getGitService();
const commandContext = {
config: this.config,
settings: this.settings,
git: gitService,
sendMessage: async (text: string) => {
await this.sendUpdate({
sessionUpdate: 'agent_message_chunk',
content: { type: 'text', text },
});
},
};
return this.commandHandler.handleCommand(commandText, commandContext);
}
private async sendUpdate(update: acp.SessionUpdate): Promise<void> {
const params: acp.SessionNotification = {
sessionId: this.id,
update,
@@ -1377,3 +1566,94 @@ function buildAvailableModes(isPlanEnabled: boolean): acp.SessionMode[] {
return modes;
}
function buildAvailableModels(
config: Config,
settings: LoadedSettings,
): {
availableModels: Array<{
modelId: string;
name: string;
description?: string;
}>;
currentModelId: string;
} {
const preferredModel = config.getModel() || DEFAULT_GEMINI_MODEL_AUTO;
const shouldShowPreviewModels = config.getHasAccessToPreviewModel();
const useGemini31 = config.getGemini31LaunchedSync?.() ?? false;
const selectedAuthType = settings.merged.security.auth.selectedType;
const useCustomToolModel =
useGemini31 && selectedAuthType === AuthType.USE_GEMINI;
const mainOptions = [
{
value: DEFAULT_GEMINI_MODEL_AUTO,
title: getDisplayString(DEFAULT_GEMINI_MODEL_AUTO),
description:
'Let Gemini CLI decide the best model for the task: gemini-2.5-pro, gemini-2.5-flash',
},
];
if (shouldShowPreviewModels) {
mainOptions.unshift({
value: PREVIEW_GEMINI_MODEL_AUTO,
title: getDisplayString(PREVIEW_GEMINI_MODEL_AUTO),
description: useGemini31
? 'Let Gemini CLI decide the best model for the task: gemini-3.1-pro, gemini-3-flash'
: 'Let Gemini CLI decide the best model for the task: gemini-3-pro, gemini-3-flash',
});
}
const manualOptions = [
{
value: DEFAULT_GEMINI_MODEL,
title: getDisplayString(DEFAULT_GEMINI_MODEL),
},
{
value: DEFAULT_GEMINI_FLASH_MODEL,
title: getDisplayString(DEFAULT_GEMINI_FLASH_MODEL),
},
{
value: DEFAULT_GEMINI_FLASH_LITE_MODEL,
title: getDisplayString(DEFAULT_GEMINI_FLASH_LITE_MODEL),
},
];
if (shouldShowPreviewModels) {
const previewProModel = useGemini31
? PREVIEW_GEMINI_3_1_MODEL
: PREVIEW_GEMINI_MODEL;
const previewProValue = useCustomToolModel
? PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL
: previewProModel;
manualOptions.unshift(
{
value: previewProValue,
title: getDisplayString(previewProModel),
},
{
value: PREVIEW_GEMINI_FLASH_MODEL,
title: getDisplayString(PREVIEW_GEMINI_FLASH_MODEL),
},
);
}
const scaleOptions = (
options: Array<{ value: string; title: string; description?: string }>,
) =>
options.map((o) => ({
modelId: o.value,
name: o.title,
description: o.description,
}));
return {
availableModels: [
...scaleOptions(mainOptions),
...scaleOptions(manualOptions),
],
currentModelId: preferredModel,
};
}
@@ -13,7 +13,7 @@ import {
type Mocked,
type Mock,
} from 'vitest';
import { GeminiAgent } from './zedIntegration.js';
import { GeminiAgent } from './acpClient.js';
import * as acp from '@agentclientprotocol/sdk';
import {
ApprovalMode,
@@ -92,7 +92,11 @@ describe('GeminiAgent Session Resume', () => {
getProjectTempDir: vi.fn().mockReturnValue('/tmp/project'),
},
getApprovalMode: vi.fn().mockReturnValue('default'),
isPlanEnabled: vi.fn().mockReturnValue(false),
isPlanEnabled: vi.fn().mockReturnValue(true),
getModel: vi.fn().mockReturnValue('gemini-pro'),
getHasAccessToPreviewModel: vi.fn().mockReturnValue(false),
getGemini31LaunchedSync: vi.fn().mockReturnValue(false),
getCheckpointingEnabled: vi.fn().mockReturnValue(false),
} as unknown as Mocked<Config>;
mockSettings = {
merged: {
@@ -200,9 +204,18 @@ describe('GeminiAgent Session Resume', () => {
name: 'YOLO',
description: 'Auto-approves all tools',
},
{
id: ApprovalMode.PLAN,
name: 'Plan',
description: 'Read-only mode',
},
],
currentModeId: ApprovalMode.DEFAULT,
},
models: {
availableModels: expect.any(Array) as unknown,
currentModelId: 'gemini-pro',
},
});
// Verify resumeChat received the correct arguments
@@ -0,0 +1,30 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { CommandHandler } from './commandHandler.js';
import { describe, it, expect } from 'vitest';
describe('CommandHandler', () => {
it('parses commands correctly', () => {
const handler = new CommandHandler();
// @ts-expect-error - testing private method
const parse = (query: string) => handler.parseSlashCommand(query);
const memShow = parse('/memory show');
expect(memShow.commandToExecute?.name).toBe('memory show');
expect(memShow.args).toBe('');
const memAdd = parse('/memory add hello world');
expect(memAdd.commandToExecute?.name).toBe('memory add');
expect(memAdd.args).toBe('hello world');
const extList = parse('/extensions list');
expect(extList.commandToExecute?.name).toBe('extensions list');
const init = parse('/init');
expect(init.commandToExecute?.name).toBe('init');
});
});
+134
View File
@@ -0,0 +1,134 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import type { Command, CommandContext } from './commands/types.js';
import { CommandRegistry } from './commands/commandRegistry.js';
import { MemoryCommand } from './commands/memory.js';
import { ExtensionsCommand } from './commands/extensions.js';
import { InitCommand } from './commands/init.js';
import { RestoreCommand } from './commands/restore.js';
export class CommandHandler {
private registry: CommandRegistry;
constructor() {
this.registry = CommandHandler.createRegistry();
}
private static createRegistry(): CommandRegistry {
const registry = new CommandRegistry();
registry.register(new MemoryCommand());
registry.register(new ExtensionsCommand());
registry.register(new InitCommand());
registry.register(new RestoreCommand());
return registry;
}
getAvailableCommands(): Array<{ name: string; description: string }> {
return this.registry.getAllCommands().map((cmd) => ({
name: cmd.name,
description: cmd.description,
}));
}
/**
* Parses and executes a command string if it matches a registered command.
* Returns true if a command was handled, false otherwise.
*/
async handleCommand(
commandText: string,
context: CommandContext,
): Promise<boolean> {
const { commandToExecute, args } = this.parseSlashCommand(commandText);
if (commandToExecute) {
await this.runCommand(commandToExecute, args, context);
return true;
}
return false;
}
private async runCommand(
commandToExecute: Command,
args: string,
context: CommandContext,
): Promise<void> {
try {
const result = await commandToExecute.execute(
context,
args ? args.split(/\s+/) : [],
);
let messageContent = '';
if (typeof result.data === 'string') {
messageContent = result.data;
} else if (
typeof result.data === 'object' &&
result.data !== null &&
'content' in result.data
) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-explicit-any
messageContent = (result.data as Record<string, any>)[
'content'
] as string;
} else {
messageContent = JSON.stringify(result.data, null, 2);
}
await context.sendMessage(messageContent);
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error);
await context.sendMessage(`Error: ${errorMessage}`);
}
}
/**
* Parses a raw slash command string into its matching headless command and arguments.
* Mirrors `packages/cli/src/utils/commands.ts` logic.
*/
private parseSlashCommand(query: string): {
commandToExecute: Command | undefined;
args: string;
} {
const trimmed = query.trim();
const parts = trimmed.substring(1).trim().split(/\s+/);
const commandPath = parts.filter((p) => p);
let currentCommands = this.registry.getAllCommands();
let commandToExecute: Command | undefined;
let pathIndex = 0;
for (const part of commandPath) {
const foundCommand = currentCommands.find((cmd) => {
const expectedName = commandPath.slice(0, pathIndex + 1).join(' ');
return (
cmd.name === part ||
cmd.name === expectedName ||
cmd.aliases?.includes(part) ||
cmd.aliases?.includes(expectedName)
);
});
if (foundCommand) {
commandToExecute = foundCommand;
pathIndex++;
if (foundCommand.subCommands) {
currentCommands = foundCommand.subCommands;
} else {
break;
}
} else {
break;
}
}
const args = parts.slice(pathIndex).join(' ');
return { commandToExecute, args };
}
}
@@ -0,0 +1,33 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { debugLogger } from '@google/gemini-cli-core';
import type { Command } from './types.js';
export class CommandRegistry {
private readonly commands = new Map<string, Command>();
register(command: Command) {
if (this.commands.has(command.name)) {
debugLogger.warn(`Command ${command.name} already registered. Skipping.`);
return;
}
this.commands.set(command.name, command);
for (const subCommand of command.subCommands ?? []) {
this.register(subCommand);
}
}
get(commandName: string): Command | undefined {
return this.commands.get(commandName);
}
getAllCommands(): Command[] {
return [...this.commands.values()];
}
}
+445
View File
@@ -0,0 +1,445 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { listExtensions } from '@google/gemini-cli-core';
import { SettingScope } from '../../config/settings.js';
import {
ExtensionManager,
inferInstallMetadata,
} from '../../config/extension-manager.js';
import { getErrorMessage } from '../../utils/errors.js';
import { McpServerEnablementManager } from '../../config/mcp/mcpServerEnablement.js';
import { stat } from 'node:fs/promises';
import type {
Command,
CommandContext,
CommandExecutionResponse,
} from './types.js';
import type { Config } from '@google/gemini-cli-core';
export class ExtensionsCommand implements Command {
readonly name = 'extensions';
readonly description = 'Manage extensions.';
readonly subCommands = [
new ListExtensionsCommand(),
new ExploreExtensionsCommand(),
new EnableExtensionCommand(),
new DisableExtensionCommand(),
new InstallExtensionCommand(),
new LinkExtensionCommand(),
new UninstallExtensionCommand(),
new RestartExtensionCommand(),
new UpdateExtensionCommand(),
];
async execute(
context: CommandContext,
_: string[],
): Promise<CommandExecutionResponse> {
return new ListExtensionsCommand().execute(context, _);
}
}
export class ListExtensionsCommand implements Command {
readonly name = 'extensions list';
readonly description = 'Lists all installed extensions.';
async execute(
context: CommandContext,
_: string[],
): Promise<CommandExecutionResponse> {
const extensions = listExtensions(context.config);
const data = extensions.length ? extensions : 'No extensions installed.';
return { name: this.name, data };
}
}
export class ExploreExtensionsCommand implements Command {
readonly name = 'extensions explore';
readonly description = 'Explore available extensions.';
async execute(
_context: CommandContext,
_: string[],
): Promise<CommandExecutionResponse> {
const extensionsUrl = 'https://geminicli.com/extensions/';
return {
name: this.name,
data: `View or install available extensions at ${extensionsUrl}`,
};
}
}
function getEnableDisableContext(
config: Config,
args: string[],
invocationName: string,
) {
const extensionManager = config.getExtensionLoader();
if (!(extensionManager instanceof ExtensionManager)) {
return {
error: `Cannot ${invocationName} extensions in this environment.`,
};
}
if (args.length === 0) {
return {
error: `Usage: /extensions ${invocationName} <extension> [--scope=<user|workspace|session>]`,
};
}
let scope = SettingScope.User;
if (args.includes('--scope=workspace') || args.includes('workspace')) {
scope = SettingScope.Workspace;
} else if (args.includes('--scope=session') || args.includes('session')) {
scope = SettingScope.Session;
}
const name = args.filter(
(a) =>
!a.startsWith('--scope') && !['user', 'workspace', 'session'].includes(a),
)[0];
let names: string[] = [];
if (name === '--all') {
let extensions = extensionManager.getExtensions();
if (invocationName === 'enable') {
extensions = extensions.filter((ext) => !ext.isActive);
}
if (invocationName === 'disable') {
extensions = extensions.filter((ext) => ext.isActive);
}
names = extensions.map((ext) => ext.name);
} else if (name) {
names = [name];
} else {
return { error: 'No extension name provided.' };
}
return { extensionManager, names, scope };
}
export class EnableExtensionCommand implements Command {
readonly name = 'extensions enable';
readonly description = 'Enable an extension.';
async execute(
context: CommandContext,
args: string[],
): Promise<CommandExecutionResponse> {
const enableContext = getEnableDisableContext(
context.config,
args,
'enable',
);
if ('error' in enableContext) {
return { name: this.name, data: enableContext.error };
}
const { names, scope, extensionManager } = enableContext;
const output: string[] = [];
for (const name of names) {
try {
await extensionManager.enableExtension(name, scope);
output.push(`Extension "${name}" enabled for scope "${scope}".`);
const extension = extensionManager
.getExtensions()
.find((e) => e.name === name);
if (extension?.mcpServers) {
const mcpEnablementManager = McpServerEnablementManager.getInstance();
const mcpClientManager = context.config.getMcpClientManager();
const enabledServers = await mcpEnablementManager.autoEnableServers(
Object.keys(extension.mcpServers),
);
if (mcpClientManager && enabledServers.length > 0) {
const restartPromises = enabledServers.map((serverName) =>
mcpClientManager.restartServer(serverName).catch((error) => {
output.push(
`Failed to restart MCP server '${serverName}': ${getErrorMessage(error)}`,
);
}),
);
await Promise.all(restartPromises);
output.push(`Re-enabled MCP servers: ${enabledServers.join(', ')}`);
}
}
} catch (e) {
output.push(`Failed to enable "${name}": ${getErrorMessage(e)}`);
}
}
return { name: this.name, data: output.join('\n') || 'No action taken.' };
}
}
export class DisableExtensionCommand implements Command {
readonly name = 'extensions disable';
readonly description = 'Disable an extension.';
async execute(
context: CommandContext,
args: string[],
): Promise<CommandExecutionResponse> {
const enableContext = getEnableDisableContext(
context.config,
args,
'disable',
);
if ('error' in enableContext) {
return { name: this.name, data: enableContext.error };
}
const { names, scope, extensionManager } = enableContext;
const output: string[] = [];
for (const name of names) {
try {
await extensionManager.disableExtension(name, scope);
output.push(`Extension "${name}" disabled for scope "${scope}".`);
} catch (e) {
output.push(`Failed to disable "${name}": ${getErrorMessage(e)}`);
}
}
return { name: this.name, data: output.join('\n') || 'No action taken.' };
}
}
export class InstallExtensionCommand implements Command {
readonly name = 'extensions install';
readonly description = 'Install an extension from a git repo or local path.';
async execute(
context: CommandContext,
args: string[],
): Promise<CommandExecutionResponse> {
const extensionLoader = context.config.getExtensionLoader();
if (!(extensionLoader instanceof ExtensionManager)) {
return {
name: this.name,
data: 'Cannot install extensions in this environment.',
};
}
const source = args.join(' ').trim();
if (!source) {
return { name: this.name, data: `Usage: /extensions install <source>` };
}
if (/[;&|`'"]/.test(source)) {
return {
name: this.name,
data: `Invalid source: contains disallowed characters.`,
};
}
try {
const installMetadata = await inferInstallMetadata(source);
const extension =
await extensionLoader.installOrUpdateExtension(installMetadata);
return {
name: this.name,
data: `Extension "${extension.name}" installed successfully.`,
};
} catch (error) {
return {
name: this.name,
data: `Failed to install extension from "${source}": ${getErrorMessage(error)}`,
};
}
}
}
export class LinkExtensionCommand implements Command {
readonly name = 'extensions link';
readonly description = 'Link an extension from a local path.';
async execute(
context: CommandContext,
args: string[],
): Promise<CommandExecutionResponse> {
const extensionLoader = context.config.getExtensionLoader();
if (!(extensionLoader instanceof ExtensionManager)) {
return {
name: this.name,
data: 'Cannot link extensions in this environment.',
};
}
const sourceFilepath = args.join(' ').trim();
if (!sourceFilepath) {
return { name: this.name, data: `Usage: /extensions link <source>` };
}
try {
await stat(sourceFilepath);
} catch (_error) {
return { name: this.name, data: `Invalid source: ${sourceFilepath}` };
}
try {
const extension = await extensionLoader.installOrUpdateExtension({
source: sourceFilepath,
type: 'link',
});
return {
name: this.name,
data: `Extension "${extension.name}" linked successfully.`,
};
} catch (error) {
return {
name: this.name,
data: `Failed to link extension: ${getErrorMessage(error)}`,
};
}
}
}
export class UninstallExtensionCommand implements Command {
readonly name = 'extensions uninstall';
readonly description = 'Uninstall an extension.';
async execute(
context: CommandContext,
args: string[],
): Promise<CommandExecutionResponse> {
const extensionLoader = context.config.getExtensionLoader();
if (!(extensionLoader instanceof ExtensionManager)) {
return {
name: this.name,
data: 'Cannot uninstall extensions in this environment.',
};
}
const all = args.includes('--all');
const names = args.filter((a) => !a.startsWith('--')).map((a) => a.trim());
if (!all && names.length === 0) {
return {
name: this.name,
data: `Usage: /extensions uninstall <extension-names...>|--all`,
};
}
let namesToUninstall: string[] = [];
if (all) {
namesToUninstall = extensionLoader.getExtensions().map((ext) => ext.name);
} else {
namesToUninstall = names;
}
if (namesToUninstall.length === 0) {
return {
name: this.name,
data: all ? 'No extensions installed.' : 'No extension name provided.',
};
}
const output: string[] = [];
for (const extensionName of namesToUninstall) {
try {
await extensionLoader.uninstallExtension(extensionName, false);
output.push(`Extension "${extensionName}" uninstalled successfully.`);
} catch (error) {
output.push(
`Failed to uninstall extension "${extensionName}": ${getErrorMessage(error)}`,
);
}
}
return { name: this.name, data: output.join('\n') };
}
}
export class RestartExtensionCommand implements Command {
readonly name = 'extensions restart';
readonly description = 'Restart an extension.';
async execute(
context: CommandContext,
args: string[],
): Promise<CommandExecutionResponse> {
const extensionLoader = context.config.getExtensionLoader();
if (!(extensionLoader instanceof ExtensionManager)) {
return { name: this.name, data: 'Cannot restart extensions.' };
}
const all = args.includes('--all');
const names = all ? null : args.filter((a) => !!a);
if (!all && names?.length === 0) {
return {
name: this.name,
data: 'Usage: /extensions restart <extension-names>|--all',
};
}
let extensionsToRestart = extensionLoader
.getExtensions()
.filter((e) => e.isActive);
if (names) {
extensionsToRestart = extensionsToRestart.filter((e) =>
names.includes(e.name),
);
}
if (extensionsToRestart.length === 0) {
return {
name: this.name,
data: 'No active extensions matched the request.',
};
}
const output: string[] = [];
for (const extension of extensionsToRestart) {
try {
await extensionLoader.restartExtension(extension);
output.push(`Restarted "${extension.name}".`);
} catch (e) {
output.push(
`Failed to restart "${extension.name}": ${getErrorMessage(e)}`,
);
}
}
return { name: this.name, data: output.join('\n') };
}
}
export class UpdateExtensionCommand implements Command {
readonly name = 'extensions update';
readonly description = 'Update an extension.';
async execute(
context: CommandContext,
args: string[],
): Promise<CommandExecutionResponse> {
const extensionLoader = context.config.getExtensionLoader();
if (!(extensionLoader instanceof ExtensionManager)) {
return { name: this.name, data: 'Cannot update extensions.' };
}
const all = args.includes('--all');
const names = all ? null : args.filter((a) => !!a);
if (!all && names?.length === 0) {
return {
name: this.name,
data: 'Usage: /extensions update <extension-names>|--all',
};
}
return {
name: this.name,
data: 'Headless extension updating requires internal UI dispatches. Please use `gemini extensions update` directly in the terminal.',
};
}
}
+62
View File
@@ -0,0 +1,62 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import * as fs from 'node:fs';
import * as path from 'node:path';
import { performInit } from '@google/gemini-cli-core';
import type {
Command,
CommandContext,
CommandExecutionResponse,
} from './types.js';
export class InitCommand implements Command {
name = 'init';
description = 'Analyzes the project and creates a tailored GEMINI.md file';
requiresWorkspace = true;
async execute(
context: CommandContext,
_args: string[] = [],
): Promise<CommandExecutionResponse> {
const targetDir = context.config.getTargetDir();
if (!targetDir) {
throw new Error('Command requires a workspace.');
}
const geminiMdPath = path.join(targetDir, 'GEMINI.md');
const result = performInit(fs.existsSync(geminiMdPath));
switch (result.type) {
case 'message':
return {
name: this.name,
data: result,
};
case 'submit_prompt':
fs.writeFileSync(geminiMdPath, '', 'utf8');
if (typeof result.content !== 'string') {
throw new Error('Init command content must be a string.');
}
// Inform the user since we can't trigger the UI-based interactive agent loop here directly.
// We output the prompt text they can use to re-trigger the generation manually,
// or just seed the GEMINI.md file as we've done above.
return {
name: this.name,
data: {
type: 'message',
messageType: 'info',
content: `A template GEMINI.md has been created at ${geminiMdPath}.\n\nTo populate it with project context, you can run the following prompt in a new chat:\n\n${result.content}`,
},
};
default:
throw new Error('Unknown result type from performInit');
}
}
}
+121
View File
@@ -0,0 +1,121 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {
addMemory,
listMemoryFiles,
refreshMemory,
showMemory,
} from '@google/gemini-cli-core';
import type {
Command,
CommandContext,
CommandExecutionResponse,
} from './types.js';
const DEFAULT_SANITIZATION_CONFIG = {
allowedEnvironmentVariables: [],
blockedEnvironmentVariables: [],
enableEnvironmentVariableRedaction: false,
};
export class MemoryCommand implements Command {
readonly name = 'memory';
readonly description = 'Manage memory.';
readonly subCommands = [
new ShowMemoryCommand(),
new RefreshMemoryCommand(),
new ListMemoryCommand(),
new AddMemoryCommand(),
];
readonly requiresWorkspace = true;
async execute(
context: CommandContext,
_: string[],
): Promise<CommandExecutionResponse> {
return new ShowMemoryCommand().execute(context, _);
}
}
export class ShowMemoryCommand implements Command {
readonly name = 'memory show';
readonly description = 'Shows the current memory contents.';
async execute(
context: CommandContext,
_: string[],
): Promise<CommandExecutionResponse> {
const result = showMemory(context.config);
return { name: this.name, data: result.content };
}
}
export class RefreshMemoryCommand implements Command {
readonly name = 'memory refresh';
readonly aliases = ['memory reload'];
readonly description = 'Refreshes the memory from the source.';
async execute(
context: CommandContext,
_: string[],
): Promise<CommandExecutionResponse> {
const result = await refreshMemory(context.config);
return { name: this.name, data: result.content };
}
}
export class ListMemoryCommand implements Command {
readonly name = 'memory list';
readonly description = 'Lists the paths of the GEMINI.md files in use.';
async execute(
context: CommandContext,
_: string[],
): Promise<CommandExecutionResponse> {
const result = listMemoryFiles(context.config);
return { name: this.name, data: result.content };
}
}
export class AddMemoryCommand implements Command {
readonly name = 'memory add';
readonly description = 'Add content to the memory.';
async execute(
context: CommandContext,
args: string[],
): Promise<CommandExecutionResponse> {
const textToAdd = args.join(' ').trim();
const result = addMemory(textToAdd);
if (result.type === 'message') {
return { name: this.name, data: result.content };
}
const toolRegistry = context.config.getToolRegistry();
const tool = toolRegistry.getTool(result.toolName);
if (tool) {
const abortController = new AbortController();
const signal = abortController.signal;
await context.sendMessage(`Saving memory via ${result.toolName}...`);
await tool.buildAndExecute(result.toolArgs, signal, undefined, {
sanitizationConfig: DEFAULT_SANITIZATION_CONFIG,
});
await refreshMemory(context.config);
return {
name: this.name,
data: `Added memory: "${textToAdd}"`,
};
} else {
return {
name: this.name,
data: `Error: Tool ${result.toolName} not found.`,
};
}
}
}
+178
View File
@@ -0,0 +1,178 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {
getCheckpointInfoList,
getToolCallDataSchema,
isNodeError,
performRestore,
} from '@google/gemini-cli-core';
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import type {
Command,
CommandContext,
CommandExecutionResponse,
} from './types.js';
export class RestoreCommand implements Command {
readonly name = 'restore';
readonly description =
'Restore to a previous checkpoint, or list available checkpoints to restore. This will reset the conversation and file history to the state it was in when the checkpoint was created';
readonly requiresWorkspace = true;
readonly subCommands = [new ListCheckpointsCommand()];
async execute(
context: CommandContext,
args: string[],
): Promise<CommandExecutionResponse> {
const { config, git: gitService } = context;
const argsStr = args.join(' ');
try {
if (!argsStr) {
return await new ListCheckpointsCommand().execute(context);
}
if (!config.getCheckpointingEnabled()) {
return {
name: this.name,
data: 'Checkpointing is not enabled. Please enable it in your settings (`general.checkpointing.enabled: true`) to use /restore.',
};
}
const selectedFile = argsStr.endsWith('.json')
? argsStr
: `${argsStr}.json`;
const checkpointDir = config.storage.getProjectTempCheckpointsDir();
const filePath = path.join(checkpointDir, selectedFile);
let data: string;
try {
data = await fs.readFile(filePath, 'utf-8');
} catch (error) {
if (isNodeError(error) && error.code === 'ENOENT') {
return {
name: this.name,
data: `File not found: ${selectedFile}`,
};
}
throw error;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const toolCallData = JSON.parse(data);
const ToolCallDataSchema = getToolCallDataSchema();
const parseResult = ToolCallDataSchema.safeParse(toolCallData);
if (!parseResult.success) {
return {
name: this.name,
data: 'Checkpoint file is invalid or corrupted.',
};
}
const restoreResultGenerator = performRestore(
parseResult.data,
gitService,
);
const restoreResult = [];
for await (const result of restoreResultGenerator) {
restoreResult.push(result);
}
// Format the result nicely since Zed just dumps data
const formattedResult = restoreResult
.map((r) => {
if (r.type === 'message') {
return `[${r.messageType.toUpperCase()}] ${r.content}`;
} else if (r.type === 'load_history') {
return `Loaded history with ${r.clientHistory.length} messages.`;
}
return `Restored: ${JSON.stringify(r)}`;
})
.join('\n');
return {
name: this.name,
data: formattedResult,
};
} catch (error) {
return {
name: this.name,
data: `An unexpected error occurred during restore: ${error}`,
};
}
}
}
export class ListCheckpointsCommand implements Command {
readonly name = 'restore list';
readonly description = 'Lists all available checkpoints.';
async execute(context: CommandContext): Promise<CommandExecutionResponse> {
const { config } = context;
try {
if (!config.getCheckpointingEnabled()) {
return {
name: this.name,
data: 'Checkpointing is not enabled. Please enable it in your settings (`general.checkpointing.enabled: true`) to use /restore.',
};
}
const checkpointDir = config.storage.getProjectTempCheckpointsDir();
try {
await fs.mkdir(checkpointDir, { recursive: true });
} catch (_e) {
// Ignore
}
const files = await fs.readdir(checkpointDir);
const jsonFiles = files.filter((file) => file.endsWith('.json'));
if (jsonFiles.length === 0) {
return { name: this.name, data: 'No checkpoints found.' };
}
const checkpointFiles = new Map<string, string>();
for (const file of jsonFiles) {
const filePath = path.join(checkpointDir, file);
const data = await fs.readFile(filePath, 'utf-8');
checkpointFiles.set(file, data);
}
const checkpointInfoList = getCheckpointInfoList(checkpointFiles);
const formatted = checkpointInfoList
.map((info) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const i = info as Record<string, any>;
const fileName = String(i['fileName'] || 'Unknown');
const toolName = String(i['toolName'] || 'Unknown');
const status = String(i['status'] || 'Unknown');
const timestamp = new Date(
Number(i['timestamp']) || 0,
).toLocaleString();
return `- **${fileName}**: ${toolName} (Status: ${status}) [${timestamp}]`;
})
.join('\n');
return {
name: this.name,
data: `Available Checkpoints:\n${formatted}`,
};
} catch (_error) {
return {
name: this.name,
data: 'An unexpected error occurred while listing checkpoints.',
};
}
}
}
+40
View File
@@ -0,0 +1,40 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import type { Config, GitService } from '@google/gemini-cli-core';
import type { LoadedSettings } from '../../config/settings.js';
export interface CommandContext {
config: Config;
settings: LoadedSettings;
git?: GitService;
sendMessage: (text: string) => Promise<void>;
}
export interface CommandArgument {
readonly name: string;
readonly description: string;
readonly isRequired?: boolean;
}
export interface Command {
readonly name: string;
readonly aliases?: string[];
readonly description: string;
readonly arguments?: CommandArgument[];
readonly subCommands?: Command[];
readonly requiresWorkspace?: boolean;
execute(
context: CommandContext,
args: string[],
): Promise<CommandExecutionResponse>;
}
export interface CommandExecutionResponse {
readonly name: string;
readonly data: unknown;
}

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