Compare commits

...

119 Commits

Author SHA1 Message Date
g-samroberts 924214b7ec docs: use starlight tabs for installation and authentication
This commit converts alternate OS and command blocks in installation.md and authentication.md to use Astro Starlight <Tabs> components.

Also updates docs-writer SKILL.md to specify the use of {/* prettier-ignore */} for MDX files as requested.
2026-04-09 17:33:57 -07:00
Abhi b238a453e3 feat(core): refactor subagent tool to unified invoke_subagent tool (#24489) 2026-04-09 16:48:24 +00:00
Jacob Richman 6686c8ee4c Support ctrl+shift+g (#25035) 2026-04-09 16:23:04 +00:00
Emily Hedlund 5724d6be0f refactor(core): use centralized path resolution for Linux sandbox (#24985) 2026-04-09 15:28:58 +00:00
Sandy Tao 615e078341 fix(sdk): skip broken sendStream tests to unblock nightly (#25000) 2026-04-09 03:39:36 +00:00
Jarrod Whelan faa7a9da30 feat(cli): refine tool output formatting for compact mode (#24677) 2026-04-09 03:30:52 +00:00
Emily Hedlund 5d589946ad refactor(sandbox): use centralized sandbox paths in macOS Seatbelt implementation (#24984) 2026-04-09 01:29:38 +00:00
Sehoon Shon 464bac270c fix(cli): optimize startup with lightweight parent process (#24667) 2026-04-09 00:17:32 +00:00
Christian Gunderman f1bb2af6de Generalize evals infra to support more types of evals, organization and queuing of named suites (#24941) 2026-04-08 23:57:26 +00:00
Jarrod Whelan bc3ed61adb feat(core): refine shell tool description display logic (#24903) 2026-04-08 23:40:43 +00:00
Jacob Richman 9c4e17b7ce Update ink version to 6.6.9 (#24980) 2026-04-08 23:36:19 +00:00
Tommaso Sciortino d06dba3538 fix(core): dynamic session ID injection to resolve resume bugs (#24972) 2026-04-08 23:27:24 +00:00
dogukanozen 80764c8bb5 fix(cli-ui): enable Ctrl+Backspace for word deletion in Windows Terminal (#21447) 2026-04-08 22:25:29 +00:00
Jarrod Whelan 14b2f35677 fix(cli): restore file path display in edit and write tool confirmations (#24974) 2026-04-08 22:19:25 +00:00
Adamya Singh 1023c5b7a6 test(sdk): add unit tests for GeminiCliSession (#21897) 2026-04-08 22:05:57 +00:00
Emily Hedlund af3638640c fix(core): resolve windows symlink bypass and stabilize sandbox integration tests (#24834) 2026-04-08 22:00:50 +00:00
Sri Pasumarthi c7b920717f feat(test-utils): add CPU performance integration test harness (#24951) 2026-04-08 21:50:29 +00:00
ruomeng 15f7b24312 feat(plan): require user confirmation for activate_skill in Plan Mode (#24946) 2026-04-08 21:44:53 +00:00
gemini-cli-robot 18cb7fd46c Changelog for v0.37.0 (#24940)
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-04-08 21:41:55 +00:00
JAYADITYA 8b01d78512 chore: ignore conductor directory (#22128)
Co-authored-by: Coco Sheng <cocosheng@google.com>
2026-04-08 20:56:02 +00:00
gemini-cli-robot 56c2397e78 Changelog for v0.38.0-preview.0 (#24938)
Co-authored-by: gemini-cli-robot <224641728+gemini-cli-robot@users.noreply.github.com>
Co-authored-by: g-samroberts <samroberts@google.com>
2026-04-08 20:45:59 +00:00
Jacob Richman d1a062bcc4 chore: update ink version to 6.6.8 (#24934) 2026-04-08 20:02:03 +00:00
Emily Hedlund a39461718c fix(core): ensure robust sandbox cleanup in all process execution paths (#24763)
Co-authored-by: Spencer <spencertang@google.com>
2026-04-08 19:03:36 +00:00
gemini-cli-robot 3df99d8bcb chore(release): bump version to 0.39.0-nightly.20260408.e77b22e63 (#24939) 2026-04-08 18:10:28 +00:00
Sandy Tao a837b39f8d feat(memory): add /memory inbox command for reviewing extracted skills (#24544) 2026-04-08 18:08:49 +00:00
Sri Pasumarthi 4ebc43bc66 feat(test-utils): add memory usage integration test harness (#24876) 2026-04-08 17:42:18 +00:00
ruomeng 34b4f1c6e4 refactor(plan): simplify policy priorities and consolidate read-only rules (#24849) 2026-04-08 15:58:29 +00:00
Gaurav e77b22e638 fix: isolate concurrent browser agent instances (#24794) 2026-04-08 14:31:10 +00:00
Adam Weidman 1b3e7d674f docs: update MCP server OAuth redirect port documentation (#24844) 2026-04-08 14:06:30 +00:00
Gaurav Ghosh e7f8d9cf1a Revert "feat: Introduce an AI-driven interactive shell mode with new"
This reverts commit 651ad63ed6.
2026-04-08 07:31:17 -07:00
Gaurav Ghosh 651ad63ed6 feat: Introduce an AI-driven interactive shell mode with new
`read-shell` and `write-to-shell` tools and a configurable mode setting.
2026-04-08 07:27:28 -07:00
Jacob Richman cbacdc67d0 feat(cli): switch to ctrl+g from ctrl-x (#24861) 2026-04-08 06:22:45 +00:00
Jacob Richman 7e1938c1bc fix(cli): switch default back to terminalBuffer=false and fix regressions introduced for that mode (#24873) 2026-04-08 05:47:54 +00:00
Anjaligarhwal b9f1d832c8 fix(core): dispose Scheduler to prevent McpProgress listener leak (#24870) 2026-04-08 03:05:53 +00:00
Dev Randalpura 47c5d25d93 Added flag for ept size and increased default size (#24859) 2026-04-08 03:03:36 +00:00
Jacob Richman 28efab483f fix(cli): always show shell command description or actual command (#24774) 2026-04-08 01:52:33 +00:00
gemini-cli-robot 9fd92c0eea Changelog for v0.37.0-preview.2 (#24848)
Co-authored-by: gemini-cli-robot <224641728+gemini-cli-robot@users.noreply.github.com>
2026-04-08 00:13:06 +00:00
Michael Bleigh 16768c08f2 refactor(core): make LegacyAgentSession dependencies optional (#24287)
Co-authored-by: Adam Weidman <adamfweidman@gmail.com>
Co-authored-by: Adam Weidman <adamfweidman@google.com>
2026-04-07 23:45:22 +00:00
JAYADITYA 1aa798dd18 refactor(cli): remove duplication in interactive shell awaiting input hint (#24801) 2026-04-07 23:36:44 +00:00
Christian Gunderman f96d5f98fe Revert "fix(ui): improve narration suppression and reduce flicker (#2… (#24857) 2026-04-07 22:45:40 +00:00
Yuna Seol 3c5b5db034 feat(core): use experiment flags for default fetch timeouts (#24261) 2026-04-07 22:35:04 +00:00
Michael Bleigh 986293bd38 feat(core): add agent protocol UI types and experimental flag (#24275)
Co-authored-by: Adam Weidman <adamfweidman@gmail.com>
Co-authored-by: Adam Weidman <adamfweidman@google.com>
2026-04-07 21:45:18 +00:00
David Pierce adf7b3b717 Improve sandbox error matching and caching (#24550) 2026-04-07 21:08:18 +00:00
Jack Wotherspoon 9637fb3990 fix(core): remove tmux alternate buffer warning (#24852) 2026-04-07 21:01:14 +00:00
Sri Pasumarthi 06fcdc231c feat(acp): add /help command (#24839) 2026-04-07 20:01:44 +00:00
Sehoon Shon d29da15427 fix(cli): prevent multiple banner increments on remount (#24843) 2026-04-07 19:44:09 +00:00
Enjoy Kumawat ab3075feb9 fix: use directory junctions on Windows for skill linking (#24823) 2026-04-07 19:28:43 +00:00
Abhi 5588000e93 chore: fix formatting for behavioral eval skill reference file (#24846) 2026-04-07 19:26:53 +00:00
krishdef7 68fef8745e fix(core): propagate BeforeModel hook model override end-to-end (#24784)
Signed-off-by: krishdef7 <gargkrish06@gmail.com>
Co-authored-by: Sandy Tao <sandytao520@icloud.com>
2026-04-07 17:49:26 +00:00
Michael Bleigh e432f7c009 feat(hooks): display hook system messages in UI (#24616) 2026-04-07 17:42:39 +00:00
Alisa 846051f716 Alisa/approve button (#24645) 2026-04-07 16:44:07 +00:00
Tommaso Sciortino 1c22c5b37b Remove flakey test (#24837) 2026-04-07 09:39:15 -07:00
Emily Hedlund 1762c9c509 temporarily disable sandbox integration test on windows (#24786) 2026-04-07 15:33:40 +00:00
Abhijit Balaji 0025978d76 feat(cli): support selective topic expansion and click-to-expand (#24793) 2026-04-07 15:00:40 +00:00
Gaurav 4c5e887732 feat(telemetry): add browser agent clearcut metrics (#24688) 2026-04-07 07:48:38 +00:00
Abhi 83096c68b0 fix(policy): allow complete_task in plan mode (#24771) 2026-04-07 03:43:42 +00:00
Christian Gunderman d2b775f9a7 Add an eval for and fix unsafe cloning behavior. (#24457) 2026-04-07 03:17:44 +00:00
Spencer 0a8da988ed fix(cli): ensure skills list outputs to stdout in non-interactive environments (#24566) 2026-04-07 02:40:23 +00:00
David Pierce 984f02c180 relax tool sandboxing overrides for plan mode to match defaults. (#24762) 2026-04-06 22:18:10 +00:00
Tommaso Sciortino df67f973ed fix(cli): respect global environment variable allowlist (#24767) 2026-04-06 22:17:55 +00:00
Christian Gunderman 7872d6d7fe fix(ui): improve narration suppression and reduce flicker (#24635) 2026-04-06 21:18:59 +00:00
Gaurav e116aa34f4 fix(browser): remove premature browser cleanup after subagent invocation (#24753) 2026-04-06 21:17:31 +00:00
Abhijit Balaji ad98294352 Revert "feat(core,cli): prioritize summary for topics (#24608)" (#24777) 2026-04-06 20:33:18 +00:00
Dev Randalpura 2353a6d253 fix(ui): fixed auth race condition causing logo to flicker (#24652) 2026-04-06 20:17:05 +00:00
krishdef7 8ac560d2c9 fix(core): handle partial llm_request in BeforeModel hook override (#22326) 2026-04-06 20:11:38 +00:00
Tommaso Sciortino c6a9d3de13 fix(cli): remove -S from shebang to fix Windows and BSD execution (#24756) 2026-04-06 19:06:56 +00:00
Christian Gunderman 8f131ffef7 Fix issue where topic headers can be posted back to back (#24759) 2026-04-06 18:36:22 +00:00
Jacob Richman 70f6d6a992 split context (#24623) 2026-04-06 17:20:38 +00:00
cynthialong0-0 c96cb09e09 feat(cli): add role specific metrics to /stats (#24659) 2026-04-06 16:20:48 +00:00
Sri Pasumarthi 15298b28c2 feat(acp): add support for /about command (#24649) 2026-04-04 07:42:27 +00:00
Gal Zahavi 7311e242ec feat(cli): enhance tool confirmation UI and selection layout (#24376) 2026-04-04 01:32:35 +00:00
Gal Zahavi 21a3925f99 docs: update sandboxing documentation and toolSandboxing settings (#24655) 2026-04-04 01:26:41 +00:00
Jacob Richman ec35ebbe57 fix(core): detect uninitialized lines (#24646) 2026-04-04 00:51:29 +00:00
Gal Zahavi 65024d4538 fix(core): ensure global temp directory is always in sandbox allowed paths (#24638)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-04-04 00:23:27 +00:00
Samee Zahid 4fb3790051 feat(core): discourage update topic tool for simple tasks (#24640)
Co-authored-by: Samee Zahid <sameez@google.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-04-03 23:52:24 +00:00
Christian Gunderman 4a040931ef Fix restoration of topic headers. (#24650) 2026-04-03 23:50:38 +00:00
Alisa e74efc5c0b Do not run pr-eval workflow when no steering changes detected (#24621) 2026-04-03 22:54:05 +00:00
Jacob Richman d5a5995281 feat(cli) Scrollbar for input prompt (#21992) 2026-04-03 22:10:04 +00:00
Gal Zahavi 893ae4d29a fix(core): ensure sandbox approvals are correctly persisted and matched for proactive expansions (#24577) 2026-04-03 21:48:18 +00:00
Emily Hedlund 370c45de67 fix(core): improve windows sandbox reliability and fix integration tests (#24480) 2026-04-03 20:54:48 +00:00
Abhi a93a1ebd65 fix(core): inject skill system instructions into subagent prompts if activated (#24620) 2026-04-03 18:45:11 +00:00
Jarrod Whelan 3f12c1d7c7 feat(cli): enable compact tool output by default (#24509) (#24510) 2026-04-03 17:59:12 +00:00
Jacob Richman 61719a3926 show color (#24613) 2026-04-03 16:53:34 +00:00
Abhijit Balaji beaa2a968b feat(core,cli): prioritize summary for topics (#24608) (#24609) 2026-04-03 16:26:38 +00:00
Emily Hedlund ca0e6f9bd9 test: fix Windows CI execution and resolve exposed platform failures (#24476) 2026-04-03 15:50:29 +00:00
Aditya Bijalwan 7a70ab9a5d Feat/browser agent metrics (#24210)
Co-authored-by: Gaurav Ghosh <gaghosh@google.com>
2026-04-03 08:21:09 +00:00
Gal Zahavi e54eecca51 feat(cli): minimalist sandbox status labels (#24582) 2026-04-03 05:22:21 +00:00
Jacob Richman 7de3e4dcf9 Add instructions for how to start react in prod and force react to prod mode (#24590) 2026-04-03 04:42:32 +00:00
Tommaso Sciortino 8c215c7a88 fix(core): remove broken PowerShell translation and fix native __write in Windows sandbox (#24571) 2026-04-03 02:50:44 +00:00
Tommaso Sciortino fe4fcf89d9 docs: clarify release coordination (#24575) 2026-04-03 01:37:35 +00:00
Jacob Richman 1f5d7014c6 feat(ui): enable "TerminalBuffer" mode to solve flicker (#24512) 2026-04-03 00:39:49 +00:00
Gal Zahavi 1ae0499e5d fix(core): pass includeDirectories to sandbox configuration (#24573) 2026-04-02 23:34:12 +00:00
Dev Randalpura 9d741abdba fix(ui): fixed table styling (#24565) 2026-04-02 22:52:25 +00:00
Ishaan Arora f54bccfe03 docs: add missing .md extensions to internal doc links (#24145) 2026-04-02 21:52:18 +00:00
gemini-cli-robot 1db169bc66 Changelog for v0.37.0-preview.1 (#24568)
Co-authored-by: gemini-cli-robot <224641728+gemini-cli-robot@users.noreply.github.com>
2026-04-02 21:31:11 +00:00
gemini-cli-robot b9b7bba48d Changelog for v0.36.0 (#24558)
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-04-02 21:10:48 +00:00
Dan Zaharia 29caa52bb7 fix(ui): hide model quota in /stats and refactor quota display (#24206) 2026-04-02 20:49:14 +00:00
Saurav Sharma e5adeaca80 fix(core): unsafe type assertions in Core File System #19712 (#19739)
Co-authored-by: Dev Randalpura <devrandalpura@google.com>
2026-04-02 20:44:22 +00:00
Adam Weidman 6fb58bd31f feat(cli): migrate nonInteractiveCli to LegacyAgentSession (#22987) 2026-04-02 20:21:40 +00:00
Jack Wotherspoon 7c3469713d docs: move agent disabling instructions and update remote agent status (#24559) 2026-04-02 20:16:01 +00:00
Jerop Kipruto 64c928fce7 feat(core): implement context-aware persistent policy approvals (#23257) 2026-04-02 20:01:33 +00:00
gemini-cli-robot 61b21e3d63 Changelog for v0.37.0-preview.0 (#24464)
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-04-02 19:52:14 +00:00
Aashir Javed 77027dff82 fix(cli): clear stale retry/loading state after cancellation (#21096) (#21960)
Co-authored-by: Aashir Javed <Aaxhirrr@users.noreply.github.com>
Co-authored-by: Dev Randalpura <devrandalpura@google.com>
2026-04-02 19:44:39 +00:00
ruomeng c0dfa1aec3 Enable 'Other' option for yesno question type (#24545) 2026-04-02 19:42:53 +00:00
Sandy Tao 06173c0885 fix(core): move project memory dir under tmp directory (#24542) 2026-04-02 17:50:43 +00:00
Jyo 08b926796c Fix crash when vim editor is not found in PATH on Windows (#22423)
Co-authored-by: Tommaso Sciortino <sciortino@gmail.com>
2026-04-02 17:40:19 +00:00
Sri Pasumarthi b80234aa3e fix(acp): handle all InvalidStreamError types gracefully in prompt (#24540) 2026-04-02 17:16:36 +00:00
Jacob Richman efbddcbf98 Update ink version to 6.6.7 (#24514) 2026-04-02 16:42:52 +00:00
Sandy Tao 63cc363606 feat(core): scope subagent workspace directories via AsyncLocalStorage (#24445) 2026-04-02 16:33:08 +00:00
joshualitt e0044f2868 feat(core): Land ContextCompressionService (#24483) 2026-04-02 16:22:04 +00:00
Gaurav beff8c91aa fix(browser): handle computer-use model detection for analyze_screenshot (#24502) 2026-04-02 15:05:21 +00:00
Coco Sheng f510394721 Implement background process monitoring and inspection tools (#23799) 2026-04-02 15:01:00 +00:00
Gaurav 811a383d50 fix: enable browser_agent in integration tests and add localhost fixture tests (#24523) 2026-04-02 14:54:38 +00:00
Gaurav 8d171e0200 docs(browser-agent): update stale browser agent documentation (#24463)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-04-02 14:52:30 +00:00
ruomeng 7b6ab50138 feat(cli): support default values for environment variables (#24469) 2026-04-02 14:38:45 +00:00
Sehoon Shon 44c8b43328 fix(cli): prevent crash in AnsiOutputText when handling non-array data (#24498) 2026-04-02 11:48:17 +00:00
Sehoon Shon 242afd49a1 revert(cli): bring back input box and footer visibility in copy mode (#24504) 2026-04-02 06:54:51 +00:00
Jarrod Whelan 66c07d7296 fix(ui): resolve unwanted vertical spacing around various tool output treatments (#24449) 2026-04-02 06:29:56 +00:00
Jacob Richman 40b73c9447 Fix shell output display (#24490) 2026-04-02 05:53:46 +00:00
507 changed files with 27020 additions and 8697 deletions
-1
View File
@@ -2,7 +2,6 @@
"experimental": {
"extensionReloading": true,
"modelSteering": true,
"memoryManager": false,
"topicUpdateNarration": true
},
"general": {
@@ -33,6 +33,35 @@ evaluation.
- **Warning**: Do not lose test fidelity by making prompts too direct/easy.
- **Primary Fix Trigger**: Adjust tool descriptions, system prompts
(`snippets.ts`), or **modules that contribute to the prompt template**.
- Fixes should generally try to improve the prompt
`@packages/core/src/prompts/snippets.ts` first.
- **Instructional Generality**: Changes to the system prompt should aim to
be as general as possible while still accomplishing the goal. Specificity
should be added only as needed.
- **Principle**: Instead of creating "forbidden lists" for specific syntax
(e.g., "Don't use `Object.create()`"), formulate a broader engineering
principle that covers the underlying issue (e.g., "Prioritize explicit
composition over hidden prototype manipulation"). This improves
steerability across a wider range of similar scenarios.
- _Low Specificity_: "Follow ecosystem best practices"
- _Medium Specificity_: "Utilize OOP and functional best practices, as
applicable"
- _High Specificity_: Provide ecosystem-specific hints as examples of a
broader principle rather than direct instructions. e.g., "NEVER use
hacks like bypassing the type system or employing 'hidden' logic (e.g.:
reflection, prototype manipulation). Instead, use explicit and idiomatic
language features (e.g.: type guards, explicit class instantiation, or
object spread) that maintain structural integrity."
- **Prompt Simplification**: Once the test is passing, use `ask_user` to
determine if prompt simplification is desired.
- **Criteria**: Simplification should be attempted only if there are
related clauses that can be de-duplicated or reparented under a single
heading.
- **Verification**: As part of simplification, you MUST identify and run
any behavioral eval tests that might be affected by the changes to
ensure no regressions are introduced.
- Test fixes should not "cheat" by changing a test's `GEMINI.md` file or by
updating the test's prompt to instruct it to not repro the bug.
- **Warning**: Prompts have multiple configurations; ensure your fix targets
the correct config for the model in question.
4. **Architecture Options**: If prompt or instruction tuning triggers no
+9 -2
View File
@@ -86,16 +86,23 @@ accessible.
- **Callouts**: Use GitHub-flavored markdown alerts to highlight important
information. To ensure the formatting is preserved by `npm run format`, place
an empty line, then the `<!-- prettier-ignore -->` comment directly before
the callout block. The callout type (`[!TYPE]`) should be on the first line,
the callout block (for `.mdx` files, use `{/* prettier-ignore */}`). The callout type (`[!TYPE]`) should be on the first line,
followed by a newline, and then the content, with each subsequent line of
content starting with `>`. Available types are `NOTE`, `TIP`, `IMPORTANT`,
`WARNING`, and `CAUTION`.
Example:
Example (`.md`):
<!-- prettier-ignore -->
> [!NOTE]
> This is an example of a multi-line note that will be preserved
> by Prettier.
Example (`.mdx`):
{/* prettier-ignore */}
> [!NOTE]
> This is an example of a multi-line note that will be preserved
> by Prettier.
### Links
+2
View File
@@ -335,6 +335,8 @@ jobs:
env:
GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}'
GEMINI_MODEL: 'gemini-3-pro-preview'
# Only run always passes behavioral tests.
EVAL_SUITE_TYPE: 'behavioral'
# Disable Vitest internal retries to avoid double-retrying;
# custom retry logic is handled in evals/test-helper.ts
VITEST_RETRY: 0
+9 -6
View File
@@ -175,10 +175,10 @@ jobs:
NO_COLOR: true
run: |
if [[ "${{ matrix.shard }}" == "cli" ]]; then
npm run test:ci --workspace @google/gemini-cli
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 -- --coverage.enabled=false
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
@@ -263,10 +263,10 @@ jobs:
NO_COLOR: true
run: |
if [[ "${{ matrix.shard }}" == "cli" ]]; then
npm run test:ci --workspace @google/gemini-cli -- --coverage.enabled=false
npm run test:ci --workspace "@google/gemini-cli" -- --coverage.enabled=false
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 -- --coverage.enabled=false
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
@@ -429,11 +429,14 @@ jobs:
NODE_ENV: 'test'
run: |
if ("${{ matrix.shard }}" -eq "cli") {
npm run test:ci --workspace @google/gemini-cli -- --coverage.enabled=false
npm run test:ci --workspace "@google/gemini-cli" -- --coverage.enabled=false
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
} 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 -- --coverage.enabled=false
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
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
npm run test:scripts
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
}
shell: 'pwsh'
+97 -25
View File
@@ -1,7 +1,7 @@
name: 'Evals: PR Evaluation & Regression'
on:
pull_request:
pull_request_target:
types: ['opened', 'synchronize', 'reopened', 'ready_for_review']
paths:
- 'packages/core/src/prompts/**'
@@ -23,13 +23,73 @@ permissions:
actions: 'read'
jobs:
detect-changes:
name: 'Detect Steering Changes'
runs-on: 'gemini-cli-ubuntu-16-core'
# Security: pull_request_target allows secrets, so we must gate carefully.
# Detection should not run code from the fork.
if: "github.repository == 'google-gemini/gemini-cli' && github.event.pull_request.draft == false"
outputs:
SHOULD_RUN: '${{ steps.detect.outputs.SHOULD_RUN }}'
STEERING_DETECTED: '${{ steps.detect.outputs.STEERING_DETECTED }}'
steps:
- name: 'Checkout'
uses: 'actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955' # ratchet:actions/checkout@v5
with:
# Check out the trusted code from main for detection
fetch-depth: 0
- name: 'Detect Steering Changes'
id: 'detect'
env:
# Use the PR's head SHA for comparison without checking it out
PR_HEAD_SHA: '${{ github.event.pull_request.head.sha }}'
run: |
# Fetch the fork's PR branch for analysis
git fetch origin pull/${{ github.event.pull_request.number }}/head:pr-head
# Run the trusted script from main
SHOULD_RUN=$(node scripts/changed_prompt.js)
STEERING_DETECTED=$(node scripts/changed_prompt.js --steering-only)
echo "SHOULD_RUN=$SHOULD_RUN" >> "$GITHUB_OUTPUT"
echo "STEERING_DETECTED=$STEERING_DETECTED" >> "$GITHUB_OUTPUT"
- name: 'Notify Approval Required'
if: "steps.detect.outputs.SHOULD_RUN == 'true'"
env:
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
run: |
RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
COMMENT_BODY="### 🛑 Action Required: Evaluation Approval
Steering changes have been detected in this PR. To prevent regressions, a maintainer must approve the evaluation run before this PR can be merged.
**Maintainers:**
1. Go to the [**Workflow Run Summary**]($RUN_URL).
2. Click the yellow **'Review deployments'** button.
3. Select the **'eval-gate'** environment and click **'Approve'**.
Once approved, the evaluation results will be posted here automatically.
<!-- eval-approval-notification -->"
# Check if comment already exists to avoid spamming
COMMENT_ID=$(gh pr view ${{ github.event.pull_request.number }} --json comments --jq '.comments[] | select(.body | contains("<!-- eval-approval-notification -->")) | .url' | grep -oE "[0-9]+$" | head -n 1)
if [ -z "$COMMENT_ID" ]; then
gh pr comment ${{ github.event.pull_request.number }} --body "$COMMENT_BODY"
else
echo "Updating existing notification comment $COMMENT_ID..."
gh api -X PATCH "repos/${{ github.repository }}/issues/comments/$COMMENT_ID" -F body="$COMMENT_BODY"
fi
pr-evaluation:
name: 'Evaluate Steering & Regressions'
needs: 'detect-changes'
if: "needs.detect-changes.outputs.SHOULD_RUN == 'true'"
# Manual approval gate via environment
environment: 'eval-gate'
runs-on: 'gemini-cli-ubuntu-16-core'
if: "github.repository == 'google-gemini/gemini-cli' && (github.event_name != 'pull_request' || (github.event.pull_request.draft == false && github.event.pull_request.head.repo.full_name == github.repository))"
# External contributors' PRs will wait for approval in this environment
environment: |-
${{ (github.event.pull_request.head.repo.full_name == github.repository) && 'internal' || 'external-evals' }}
env:
# CENTRALIZED MODEL LIST
MODEL_LIST: 'gemini-3-flash-preview'
@@ -38,8 +98,26 @@ jobs:
- name: 'Checkout'
uses: 'actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955' # ratchet:actions/checkout@v5
with:
# Check out the fork's PR code for the actual evaluation
# This only runs AFTER manual approval
ref: '${{ github.event.pull_request.head.sha }}'
fetch-depth: 0
- name: 'Remove Approval Notification'
# Run even if other steps fail, to ensure we clean up the "Action Required" message
if: 'always()'
env:
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
PR_NUMBER: '${{ github.event.pull_request.number }}'
run: |
echo "Debug: PR_NUMBER is '$PR_NUMBER'"
# Search for the notification comment by its hidden tag
COMMENT_ID=$(gh pr view "$PR_NUMBER" --json comments --jq '.comments[] | select(.body | contains("<!-- eval-approval-notification -->")) | .url' | grep -oE "[0-9]+$" | head -n 1)
if [ -n "$COMMENT_ID" ]; then
echo "Removing notification comment $COMMENT_ID now that run is approved..."
gh api -X DELETE "repos/${{ github.repository }}/issues/comments/$COMMENT_ID"
fi
- name: 'Set up Node.js'
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4.4.0
with:
@@ -52,16 +130,8 @@ jobs:
- name: 'Build project'
run: 'npm run build'
- name: 'Detect Steering Changes'
id: 'detect'
run: |
SHOULD_RUN=$(node scripts/changed_prompt.js)
STEERING_DETECTED=$(node scripts/changed_prompt.js --steering-only)
echo "SHOULD_RUN=$SHOULD_RUN" >> "$GITHUB_OUTPUT"
echo "STEERING_DETECTED=$STEERING_DETECTED" >> "$GITHUB_OUTPUT"
- name: 'Analyze PR Content (Guidance)'
if: "steps.detect.outputs.STEERING_DETECTED == 'true'"
if: "needs.detect-changes.outputs.STEERING_DETECTED == 'true'"
id: 'analysis'
env:
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
@@ -79,7 +149,6 @@ jobs:
fi
- name: 'Execute Regression Check'
if: "steps.detect.outputs.SHOULD_RUN == 'true'"
env:
GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}'
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
@@ -94,7 +163,7 @@ jobs:
fi
- name: 'Post or Update PR Comment'
if: "always() && steps.detect.outputs.STEERING_DETECTED == 'true'"
if: "always() && (needs.detect-changes.outputs.STEERING_DETECTED == 'true' || env.REPORT_FILE != '')"
env:
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
run: |
@@ -104,17 +173,20 @@ jobs:
cat eval_regression_report.md
echo ""
fi
echo "### 🧠 Model Steering Guidance"
echo ""
echo "This PR modifies files that affect the model's behavior (prompts, tools, or instructions)."
echo ""
if [[ "${{ steps.analysis.outputs.MISSING_EVALS }}" == "true" ]]; then
echo "- ⚠️ **Consider adding Evals:** No behavioral evaluations (\`evals/*.eval.ts\`) were added or updated in this PR. Consider [adding a test case](https://github.com/google-gemini/gemini-cli/blob/main/evals/README.md#creating-an-evaluation) to verify the new behavior and prevent regressions."
fi
if [[ "${{ needs.detect-changes.outputs.STEERING_DETECTED }}" == "true" ]]; then
echo "### 🧠 Model Steering Guidance"
echo ""
echo "This PR modifies files that affect the model's behavior (prompts, tools, or instructions)."
echo ""
if [[ "${{ steps.analysis.outputs.IS_MAINTAINER }}" == "true" ]]; then
echo "- 🚀 **Maintainer Reminder:** Please ensure that these changes do not regress results on benchmark evals before merging."
if [[ "${{ steps.analysis.outputs.MISSING_EVALS }}" == "true" ]]; then
echo "- ⚠️ **Consider adding Evals:** No behavioral evaluations (\`evals/*.eval.ts\`) were added or updated in this PR. Consider [adding a test case](https://github.com/google-gemini/gemini-cli/blob/main/evals/README.md#creating-an-evaluation) to verify the new behavior and prevent regressions."
fi
if [[ "${{ steps.analysis.outputs.IS_MAINTAINER }}" == "true" ]]; then
echo "- 🚀 **Maintainer Reminder:** Please ensure that these changes do not regress results on benchmark evals before merging."
fi
fi
echo ""
+15 -5
View File
@@ -5,10 +5,18 @@ on:
- cron: '0 1 * * *' # Runs at 1 AM every day
workflow_dispatch:
inputs:
run_all:
description: 'Run all evaluations (including usually passing)'
type: 'boolean'
default: true
suite_type:
description: 'Suite type to run'
type: 'choice'
options:
- 'behavioral'
- 'component-level'
- 'hero-scenario'
default: 'behavioral'
suite_name:
description: 'Specific suite name to run'
required: false
type: 'string'
test_name_pattern:
description: 'Test name pattern or file name'
required: false
@@ -59,7 +67,9 @@ jobs:
env:
GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}'
GEMINI_MODEL: '${{ matrix.model }}'
RUN_EVALS: "${{ github.event.inputs.run_all != 'false' }}"
RUN_EVALS: 'true'
EVAL_SUITE_TYPE: "${{ github.event.inputs.suite_type || 'behavioral' }}"
EVAL_SUITE_NAME: '${{ github.event.inputs.suite_name }}'
TEST_NAME_PATTERN: '${{ github.event.inputs.test_name_pattern }}'
# Disable Vitest internal retries to avoid double-retrying;
# custom retry logic is handled in evals/test-helper.ts
+33
View File
@@ -0,0 +1,33 @@
name: 'Memory Tests: Nightly'
on:
schedule:
- cron: '0 2 * * *' # Runs at 2 AM every day
workflow_dispatch: # Allow manual trigger
permissions:
contents: 'read'
jobs:
memory-test:
name: 'Run Memory Usage Tests'
runs-on: 'gemini-cli-ubuntu-16-core'
if: "github.repository == 'google-gemini/gemini-cli'"
steps:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
- name: 'Set up Node.js'
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: 'Install dependencies'
run: 'npm ci'
- name: 'Build project'
run: 'npm run build'
- name: 'Run Memory Tests'
run: 'npm run test:memory'
+33
View File
@@ -0,0 +1,33 @@
name: 'Performance Tests: Nightly'
on:
schedule:
- cron: '0 3 * * *' # Runs at 3 AM every day
workflow_dispatch: # Allow manual trigger
permissions:
contents: 'read'
jobs:
perf-test:
name: 'Run Performance Usage Tests'
runs-on: 'gemini-cli-ubuntu-16-core'
if: "github.repository == 'google-gemini/gemini-cli'"
steps:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
- name: 'Set up Node.js'
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: 'Install dependencies'
run: 'npm ci'
- name: 'Build project'
run: 'npm run build'
- name: 'Run Performance Tests'
run: 'npm run test:perf'
+4
View File
@@ -48,6 +48,7 @@ packages/cli/src/generated/
packages/core/src/generated/
packages/devtools/src/_client-assets.ts
.integration-tests/
.perf-tests/
packages/vscode-ide-companion/*.vsix
packages/cli/download-ripgrep*/
@@ -64,3 +65,6 @@ gemini-debug.log
evals/logs/
temp_agents/
# conductor extension and planning directories
conductor/
+7
View File
@@ -44,6 +44,13 @@ powerful tool for developers.
- **Test Commands:**
- **Unit (All):** `npm run test`
- **Integration (E2E):** `npm run test:e2e`
- > **NOTE**: Please run the memory and perf tests locally **only if** you are
> implementing changes related to those test areas. Otherwise skip these
> tests locally and rely on CI to run them on nightly builds.
- **Memory (Nightly):** `npm run test:memory` (Runs memory regression tests
against baselines. Excluded from `preflight`, run nightly.)
- **Performance (Nightly):** `npm run test:perf` (Runs CPU performance
regression tests against baselines. Excluded from `preflight`, run nightly.)
- **Workspace-Specific:** `npm test -w <pkg> -- <path>` (Note: `<path>` must
be relative to the workspace root, e.g.,
`-w @google/gemini-cli-core -- src/routing/modelRouterService.test.ts`)
+46
View File
@@ -18,6 +18,52 @@ on GitHub.
| [Preview](preview.md) | Experimental features ready for early feedback. |
| [Stable](latest.md) | Stable, recommended for general use. |
## Announcements: v0.37.0 - 2026-04-08
- **Dynamic Sandbox Expansion:** Implemented dynamic sandbox expansion and
worktree support for Linux and Windows, improving developer workflows in
isolated environments
([#23692](https://github.com/google-gemini/gemini-cli/pull/23692) by @galz10,
[#23691](https://github.com/google-gemini/gemini-cli/pull/23691) by
@scidomino).
- **Chapters Narrative Flow:** Introduced tool-based topic grouping ("Chapters")
to provide better session structure and narrative continuity
([#23150](https://github.com/google-gemini/gemini-cli/pull/23150) by
@Abhijit-2592,
[#24079](https://github.com/google-gemini/gemini-cli/pull/24079) by
@gundermanc).
- **Advanced Browser Capabilities:** Enhanced the browser agent with persistent
sessions and dynamic tool discovery
([#21306](https://github.com/google-gemini/gemini-cli/pull/21306) by
@kunal-10-cloud,
[#23805](https://github.com/google-gemini/gemini-cli/pull/23805) by
@cynthialong0-0).
## Announcements: v0.36.0 - 2026-04-01
- **Multi-Registry Architecture and Sandboxing:** Introduced a multi-registry
architecture and implemented native macOS Seatbelt and Windows sandboxing for
enhanced subagent security
([#22712](https://github.com/google-gemini/gemini-cli/pull/22712),
[#22718](https://github.com/google-gemini/gemini-cli/pull/22718) by @akh64bit,
[#22832](https://github.com/google-gemini/gemini-cli/pull/22832) by @ehedlund,
[#21807](https://github.com/google-gemini/gemini-cli/pull/21807) by
@mattKorwel).
- **Refreshed Composer UX:** Implemented a refreshed user experience for the
Composer layout and improved terminal interaction robustness
([#21212](https://github.com/google-gemini/gemini-cli/pull/21212),
[#23286](https://github.com/google-gemini/gemini-cli/pull/23286) by
@jwhelangoog).
- **Git Worktree Support:** Added native support for Git worktrees, allowing for
isolated parallel sessions
([#22973](https://github.com/google-gemini/gemini-cli/pull/22973),
[#23265](https://github.com/google-gemini/gemini-cli/pull/23265) by @jerop).
- **Subagent Context and Feedback:** Enhanced subagents with JIT context
injection and resilient tool rejection with contextual feedback
([#23032](https://github.com/google-gemini/gemini-cli/pull/23032),
[#22951](https://github.com/google-gemini/gemini-cli/pull/22951) by
@abhipatel12).
## Announcements: v0.35.0 - 2026-03-24
- **Customizable Keyboard Shortcuts:** Users can now customize their keyboard
+396 -368
View File
@@ -1,6 +1,6 @@
# Latest stable release: v0.35.3
# Latest stable release: v0.37.0
Released: March 28, 2026
Released: April 08, 2026
For most users, our latest stable release is the recommended release. Install
the latest stable version with:
@@ -11,381 +11,409 @@ npm install -g @google/gemini-cli
## Highlights
- **Customizable Keyboard Shortcuts:** Significant improvements to input
flexibility with support for custom keybindings, literal character bindings,
and extended terminal protocol keys.
- **Vim Mode Enhancements:** Further refinement of the Vim modal editing
experience, adding common motions like \`X\`, \`~\`, \`r\`, and \`f/F/t/T\`,
along with yank and paste support.
- **Enhanced Security through Sandboxing:** Introduction of a unified
\`SandboxManager\` and integration of Linux-native sandboxing (bubblewrap and
seccomp) to isolate tool execution and improve system security.
- **JIT Context Discovery:** Improved performance and accuracy by enabling
Just-In-Time context loading for file system tools, ensuring the model has the
most relevant information without overwhelming the context.
- **Subagent & Performance Updates:** Subagents are now enabled by default,
supported by a model-driven parallel tool scheduler and code splitting for
faster startup and more efficient task execution.
- **Dynamic Sandbox Expansion:** Implemented dynamic sandbox expansion and
worktree support for both Linux and Windows, enhancing development flexibility
in restricted environments.
- **Tool-Based Topic Grouping (Chapters):** Introduced "Chapters" to logically
group agent interactions based on tool usage and intent, providing a clearer
narrative flow in long sessions.
- **Enhanced Browser Agent:** Added persistent session management, dynamic
read-only tool discovery, and sandbox-aware initialization for the browser
agent.
- **Security & Permission Hardening:** Implemented secret visibility lockdown
for environment files and integrated integrity controls for Windows
sandboxing.
## What's Changed
- fix(patch): cherry-pick 765fb67 to release/v0.35.2-pr-24055 [CONFLICTS] by
@gemini-cli-robot in
[#24063](https://github.com/google-gemini/gemini-cli/pull/24063)
- feat(evals): centralize test agents into test-utils for reuse by @Samee24 in
[#23616](https://github.com/google-gemini/gemini-cli/pull/23616)
- revert: chore(config): disable agents by default by @abhipatel12 in
[#23672](https://github.com/google-gemini/gemini-cli/pull/23672)
- fix(plan): update telemetry attribute keys and add timestamp by @Adib234 in
[#23685](https://github.com/google-gemini/gemini-cli/pull/23685)
- fix(core): prevent premature MCP discovery completion by @jackwotherspoon in
[#23637](https://github.com/google-gemini/gemini-cli/pull/23637)
- feat(browser): add maxActionsPerTask for browser agent setting by
@cynthialong0-0 in
[#23216](https://github.com/google-gemini/gemini-cli/pull/23216)
- fix(core): improve agent loader error formatting for empty paths by
@adamfweidman in
[#23690](https://github.com/google-gemini/gemini-cli/pull/23690)
- fix(cli): only show updating spinner when auto-update is in progress by
@scidomino in [#23709](https://github.com/google-gemini/gemini-cli/pull/23709)
- Refine onboarding metrics to log the duration explicitly and use the tier
name. by @yunaseoul in
[#23678](https://github.com/google-gemini/gemini-cli/pull/23678)
- chore(tools): add toJSON to tools and invocations to reduce logging verbosity
by @alisa-alisa in
[#22899](https://github.com/google-gemini/gemini-cli/pull/22899)
- fix(cli): stabilize copy mode to prevent flickering and cursor resets by
@mattKorwel in
[#22584](https://github.com/google-gemini/gemini-cli/pull/22584)
- fix(test): move flaky ctrl-c-exit test to non-blocking suite by @mattKorwel in
[#23732](https://github.com/google-gemini/gemini-cli/pull/23732)
- feat(skills): add ci skill for automated failure replication by @mattKorwel in
[#23720](https://github.com/google-gemini/gemini-cli/pull/23720)
- feat(sandbox): implement forbiddenPaths for OS-specific sandbox managers by
@ehedlund in [#23282](https://github.com/google-gemini/gemini-cli/pull/23282)
- fix(core): conditionally expose additional_permissions in shell tool by
@galz10 in [#23729](https://github.com/google-gemini/gemini-cli/pull/23729)
- refactor(core): standardize OS-specific sandbox tests and extract linux helper
methods by @ehedlund in
[#23715](https://github.com/google-gemini/gemini-cli/pull/23715)
- format recently added script by @scidomino in
[#23739](https://github.com/google-gemini/gemini-cli/pull/23739)
- fix(ui): prevent over-eager slash subcommand completion by @keithguerin in
[#20136](https://github.com/google-gemini/gemini-cli/pull/20136)
- Fix dynamic model routing for gemini 3.1 pro to customtools model by
@kevinjwang1 in
[#23641](https://github.com/google-gemini/gemini-cli/pull/23641)
- feat(core): support inline agentCardJson for remote agents by @adamfweidman in
[#23743](https://github.com/google-gemini/gemini-cli/pull/23743)
- fix(cli): skip console log/info in headless mode by @cynthialong0-0 in
[#22739](https://github.com/google-gemini/gemini-cli/pull/22739)
- test(core): install bubblewrap on Linux CI for sandbox integration tests by
@ehedlund in [#23583](https://github.com/google-gemini/gemini-cli/pull/23583)
- docs(reference): split tools table into category sections by @sheikhlimon in
[#21516](https://github.com/google-gemini/gemini-cli/pull/21516)
- fix(browser): detect embedded URLs in query params to prevent allowedDomains
bypass by @tony-shi in
[#23225](https://github.com/google-gemini/gemini-cli/pull/23225)
- fix(browser): add proxy bypass constraint to domain restriction system prompt
by @tony-shi in
[#23229](https://github.com/google-gemini/gemini-cli/pull/23229)
- fix(policy): relax write_file argsPattern in plan mode to allow paths without
session ID by @Adib234 in
[#23695](https://github.com/google-gemini/gemini-cli/pull/23695)
- docs: fix grammar in CONTRIBUTING and numbering in sandbox docs by
@splint-disk-8i in
[#23448](https://github.com/google-gemini/gemini-cli/pull/23448)
- fix(acp): allow attachments by adding a permission prompt by @sripasg in
[#23680](https://github.com/google-gemini/gemini-cli/pull/23680)
- fix(core): thread AbortSignal to chat compression requests (#20405) by
@SH20RAJ in [#20778](https://github.com/google-gemini/gemini-cli/pull/20778)
- feat(core): implement Windows sandbox dynamic expansion Phase 1 and 2.1 by
@scidomino in [#23691](https://github.com/google-gemini/gemini-cli/pull/23691)
- Add note about root privileges in sandbox docs by @diodesign in
[#23314](https://github.com/google-gemini/gemini-cli/pull/23314)
- docs(core): document agent_card_json string literal options for remote agents
by @adamfweidman in
[#23797](https://github.com/google-gemini/gemini-cli/pull/23797)
- fix(cli): resolve TTY hang on headless environments by unconditionally
resuming process.stdin before React Ink launch by @cocosheng-g in
[#23673](https://github.com/google-gemini/gemini-cli/pull/23673)
- fix(ui): cleanup estimated string length hacks in composer by @keithguerin in
[#23694](https://github.com/google-gemini/gemini-cli/pull/23694)
- feat(browser): dynamically discover read-only tools by @cynthialong0-0 in
[#23805](https://github.com/google-gemini/gemini-cli/pull/23805)
- docs: clarify policy requirement for `general.plan.directory` in settings
schema by @jerop in
[#23784](https://github.com/google-gemini/gemini-cli/pull/23784)
- Revert "perf(cli): optimize --version startup time (#23671)" by @scidomino in
[#23812](https://github.com/google-gemini/gemini-cli/pull/23812)
- don't silence errors from wombat by @scidomino in
[#23822](https://github.com/google-gemini/gemini-cli/pull/23822)
- fix(ui): prevent escape key from cancelling requests in shell mode by
@PrasannaPal21 in
[#21245](https://github.com/google-gemini/gemini-cli/pull/21245)
- Changelog for v0.36.0-preview.0 by @gemini-cli-robot in
[#23702](https://github.com/google-gemini/gemini-cli/pull/23702)
- feat(core,ui): Add experiment-gated support for gemini flash 3.1 lite by
@chrstnb in [#23794](https://github.com/google-gemini/gemini-cli/pull/23794)
- Changelog for v0.36.0-preview.3 by @gemini-cli-robot in
[#23827](https://github.com/google-gemini/gemini-cli/pull/23827)
- new linting check: github-actions-pinning by @alisa-alisa in
[#23808](https://github.com/google-gemini/gemini-cli/pull/23808)
- fix(cli): show helpful guidance when no skills are available by @Niralisj in
[#23785](https://github.com/google-gemini/gemini-cli/pull/23785)
- fix: Chat logs and errors handle tail tool calls correctly by @googlestrobe in
[#22460](https://github.com/google-gemini/gemini-cli/pull/22460)
- Don't try removing a tag from a non-existent release. by @scidomino in
[#23830](https://github.com/google-gemini/gemini-cli/pull/23830)
- fix(cli): allow ask question dialog to take full window height by @jacob314 in
[#23693](https://github.com/google-gemini/gemini-cli/pull/23693)
- fix(core): strip leading underscores from error types in telemetry by
@yunaseoul in [#23824](https://github.com/google-gemini/gemini-cli/pull/23824)
- Changelog for v0.35.0 by @gemini-cli-robot in
[#23819](https://github.com/google-gemini/gemini-cli/pull/23819)
- feat(evals): add reliability harvester and 500/503 retry support by
@alisa-alisa in
[#23626](https://github.com/google-gemini/gemini-cli/pull/23626)
- feat(sandbox): dynamic Linux sandbox expansion and worktree support by @galz10
in [#23692](https://github.com/google-gemini/gemini-cli/pull/23692)
- Merge examples of use into quickstart documentation by @diodesign in
[#23319](https://github.com/google-gemini/gemini-cli/pull/23319)
- fix(cli): prioritize primary name matches in slash command search by @sehoon38
in [#23850](https://github.com/google-gemini/gemini-cli/pull/23850)
- Changelog for v0.35.1 by @gemini-cli-robot in
[#23840](https://github.com/google-gemini/gemini-cli/pull/23840)
- fix(browser): keep input blocker active across navigations by @kunal-10-cloud
in [#22562](https://github.com/google-gemini/gemini-cli/pull/22562)
- feat(core): new skill to look for duplicated code while reviewing PRs by
@devr0306 in [#23704](https://github.com/google-gemini/gemini-cli/pull/23704)
- fix(core): replace hardcoded non-interactive ASK_USER denial with explicit
policy rules by @ruomengz in
[#23668](https://github.com/google-gemini/gemini-cli/pull/23668)
- fix(plan): after exiting plan mode switches model to a flash model by @Adib234
in [#23885](https://github.com/google-gemini/gemini-cli/pull/23885)
- feat(gcp): add development worker infrastructure by @mattKorwel in
[#23814](https://github.com/google-gemini/gemini-cli/pull/23814)
- fix(a2a-server): A2A server should execute ask policies in interactive mode by
@kschaab in [#23831](https://github.com/google-gemini/gemini-cli/pull/23831)
- feat(core): define TrajectoryProvider interface by @sehoon38 in
[#23050](https://github.com/google-gemini/gemini-cli/pull/23050)
- Docs: Update quotas and pricing by @jkcinouye in
[#23835](https://github.com/google-gemini/gemini-cli/pull/23835)
- fix(core): allow disabling environment variable redaction by @galz10 in
[#23927](https://github.com/google-gemini/gemini-cli/pull/23927)
- fix(a2a-server): A2A server should execute ask policies in interactive mode by
@keith.schaab in
[#23831](https://github.com/google-gemini/gemini-cli/pull/23831)
- feat(cli): customizable keyboard shortcuts by @scidomino in
[#21945](https://github.com/google-gemini/gemini-cli/pull/21945)
- feat(core): Thread `AgentLoopContext` through core. by @joshualitt in
[#21944](https://github.com/google-gemini/gemini-cli/pull/21944)
- chore(release): bump version to 0.35.0-nightly.20260311.657f19c1f by
@gemini-cli-robot in
[#21966](https://github.com/google-gemini/gemini-cli/pull/21966)
- refactor(a2a): remove legacy CoreToolScheduler by @adamfweidman in
[#21955](https://github.com/google-gemini/gemini-cli/pull/21955)
- feat(ui): add missing vim mode motions (X, ~, r, f/F/t/T, df/dt and friends)
by @aanari in [#21932](https://github.com/google-gemini/gemini-cli/pull/21932)
- Feat/retry fetch notifications by @aishaneeshah in
[#21813](https://github.com/google-gemini/gemini-cli/pull/21813)
- fix(core): remove OAuth check from handle fallback and clean up stray file by
@sehoon38 in [#21962](https://github.com/google-gemini/gemini-cli/pull/21962)
- feat(cli): support literal character keybindings and extended Kitty protocol
keys by @scidomino in
[#21972](https://github.com/google-gemini/gemini-cli/pull/21972)
- fix(ui): clamp cursor to last char after all NORMAL mode deletes by @aanari in
[#21973](https://github.com/google-gemini/gemini-cli/pull/21973)
- test(core): add missing tests for prompts/utils.ts by @krrishverma1805-web in
[#19941](https://github.com/google-gemini/gemini-cli/pull/19941)
- fix(cli): allow scrolling keys in copy mode (Ctrl+S selection mode) by
@nsalerni in [#19933](https://github.com/google-gemini/gemini-cli/pull/19933)
- docs(cli): add custom keybinding documentation by @scidomino in
[#21980](https://github.com/google-gemini/gemini-cli/pull/21980)
- docs: fix misleading YOLO mode description in defaultApprovalMode by
@Gyanranjan-Priyam in
[#21878](https://github.com/google-gemini/gemini-cli/pull/21878)
- fix: clean up /clear and /resume by @jackwotherspoon in
[#22007](https://github.com/google-gemini/gemini-cli/pull/22007)
- fix(core)#20941: reap orphaned descendant processes on PTY abort by @manavmax
in [#21124](https://github.com/google-gemini/gemini-cli/pull/21124)
- fix(core): update language detection to use LSP 3.18 identifiers by @yunaseoul
in [#21931](https://github.com/google-gemini/gemini-cli/pull/21931)
- feat(cli): support removing keybindings via '-' prefix by @scidomino in
[#22042](https://github.com/google-gemini/gemini-cli/pull/22042)
- feat(policy): add --admin-policy flag for supplemental admin policies by
@galz10 in [#20360](https://github.com/google-gemini/gemini-cli/pull/20360)
- merge duplicate imports packages/cli/src subtask1 by @Nixxx19 in
[#22040](https://github.com/google-gemini/gemini-cli/pull/22040)
- perf(core): parallelize user quota and experiments fetching in refreshAuth by
@sehoon38 in [#21648](https://github.com/google-gemini/gemini-cli/pull/21648)
- Changelog for v0.34.0-preview.0 by @gemini-cli-robot in
[#21965](https://github.com/google-gemini/gemini-cli/pull/21965)
- Changelog for v0.33.0 by @gemini-cli-robot in
[#21967](https://github.com/google-gemini/gemini-cli/pull/21967)
- fix(core): handle EISDIR in robustRealpath on Windows by @sehoon38 in
[#21984](https://github.com/google-gemini/gemini-cli/pull/21984)
- feat(core): include initiationMethod in conversation interaction telemetry by
@yunaseoul in [#22054](https://github.com/google-gemini/gemini-cli/pull/22054)
- feat(ui): add vim yank/paste (y/p/P) with unnamed register by @aanari in
[#22026](https://github.com/google-gemini/gemini-cli/pull/22026)
- fix(core): enable numerical routing for api key users by @sehoon38 in
[#21977](https://github.com/google-gemini/gemini-cli/pull/21977)
- feat(telemetry): implement retry attempt telemetry for network related retries
by @aishaneeshah in
[#22027](https://github.com/google-gemini/gemini-cli/pull/22027)
- fix(policy): remove unnecessary escapeRegex from pattern builders by
@spencer426 in
[#21921](https://github.com/google-gemini/gemini-cli/pull/21921)
- fix(core): preserve dynamic tool descriptions on session resume by @sehoon38
in [#18835](https://github.com/google-gemini/gemini-cli/pull/18835)
- chore: allow 'gemini-3.1' in sensitive keyword linter by @scidomino in
[#22065](https://github.com/google-gemini/gemini-cli/pull/22065)
- feat(core): support custom base URL via env vars by @junaiddshaukat in
[#21561](https://github.com/google-gemini/gemini-cli/pull/21561)
- merge duplicate imports packages/cli/src subtask2 by @Nixxx19 in
[#22051](https://github.com/google-gemini/gemini-cli/pull/22051)
- fix(core): silently retry API errors up to 3 times before halting session by
@spencer426 in
[#21989](https://github.com/google-gemini/gemini-cli/pull/21989)
- feat(core): simplify subagent success UI and improve early termination display
- feat(cli): enable notifications cross-platform via terminal bell fallback by
@genneth in [#21618](https://github.com/google-gemini/gemini-cli/pull/21618)
- feat(sandbox): implement secret visibility lockdown for env files by
@DavidAPierce in
[#23712](https://github.com/google-gemini/gemini-cli/pull/23712)
- fix(core): remove shell outputChunks buffer caching to prevent memory bloat
and sanitize prompt input by @spencer426 in
[#23751](https://github.com/google-gemini/gemini-cli/pull/23751)
- feat(core): implement persistent browser session management by @kunal-10-cloud
in [#21306](https://github.com/google-gemini/gemini-cli/pull/21306)
- refactor(core): delegate sandbox denial parsing to SandboxManager by
@scidomino in [#23928](https://github.com/google-gemini/gemini-cli/pull/23928)
- dep(update) Update Ink version to 6.5.0 by @jacob314 in
[#23843](https://github.com/google-gemini/gemini-cli/pull/23843)
- Docs: Update 'docs-writer' skill for relative links by @jkcinouye in
[#21463](https://github.com/google-gemini/gemini-cli/pull/21463)
- Changelog for v0.36.0-preview.4 by @gemini-cli-robot in
[#23935](https://github.com/google-gemini/gemini-cli/pull/23935)
- fix(acp): Update allow approval policy flow for ACP clients to fix config
persistence and compatible with TUI by @sripasg in
[#23818](https://github.com/google-gemini/gemini-cli/pull/23818)
- Changelog for v0.35.2 by @gemini-cli-robot in
[#23960](https://github.com/google-gemini/gemini-cli/pull/23960)
- ACP integration documents by @g-samroberts in
[#22254](https://github.com/google-gemini/gemini-cli/pull/22254)
- fix(core): explicitly set error names to avoid bundling renaming issues by
@yunaseoul in [#23913](https://github.com/google-gemini/gemini-cli/pull/23913)
- feat(core): subagent isolation and cleanup hardening by @abhipatel12 in
[#23903](https://github.com/google-gemini/gemini-cli/pull/23903)
- disable extension-reload test by @scidomino in
[#24018](https://github.com/google-gemini/gemini-cli/pull/24018)
- feat(core): add forbiddenPaths to GlobalSandboxOptions and refactor
createSandboxManager by @ehedlund in
[#23936](https://github.com/google-gemini/gemini-cli/pull/23936)
- refactor(core): improve ignore resolution and fix directory-matching bug by
@ehedlund in [#23816](https://github.com/google-gemini/gemini-cli/pull/23816)
- revert(core): support custom base URL via env vars by @spencer426 in
[#23976](https://github.com/google-gemini/gemini-cli/pull/23976)
- Increase memory limited for eslint. by @jacob314 in
[#24022](https://github.com/google-gemini/gemini-cli/pull/24022)
- fix(acp): prevent crash on empty response in ACP mode by @sripasg in
[#23952](https://github.com/google-gemini/gemini-cli/pull/23952)
- feat(core): Land `AgentHistoryProvider`. by @joshualitt in
[#23978](https://github.com/google-gemini/gemini-cli/pull/23978)
- fix(core): switch to subshells for shell tool wrapping to fix heredocs and
edge cases by @abhipatel12 in
[#24024](https://github.com/google-gemini/gemini-cli/pull/24024)
- Debug command. by @jacob314 in
[#23851](https://github.com/google-gemini/gemini-cli/pull/23851)
- Changelog for v0.36.0-preview.5 by @gemini-cli-robot in
[#24046](https://github.com/google-gemini/gemini-cli/pull/24046)
- Fix test flakes by globally mocking ink-spinner by @jacob314 in
[#24044](https://github.com/google-gemini/gemini-cli/pull/24044)
- Enable network access in sandbox configuration by @galz10 in
[#24055](https://github.com/google-gemini/gemini-cli/pull/24055)
- feat(context): add configurable memoryBoundaryMarkers setting by @SandyTao520
in [#24020](https://github.com/google-gemini/gemini-cli/pull/24020)
- feat(core): implement windows sandbox expansion and denial detection by
@scidomino in [#24027](https://github.com/google-gemini/gemini-cli/pull/24027)
- fix(core): resolve ACP Operation Aborted Errors in grep_search by @ivanporty
in [#23821](https://github.com/google-gemini/gemini-cli/pull/23821)
- fix(hooks): prevent SessionEnd from firing twice in non-interactive mode by
@krishdef7 in [#22139](https://github.com/google-gemini/gemini-cli/pull/22139)
- Re-word intro to Gemini 3 page. by @g-samroberts in
[#24069](https://github.com/google-gemini/gemini-cli/pull/24069)
- fix(cli): resolve layout contention and flashing loop in StatusRow by
@keithguerin in
[#24065](https://github.com/google-gemini/gemini-cli/pull/24065)
- fix(sandbox): implement Windows Mandatory Integrity Control for GeminiSandbox
by @galz10 in [#24057](https://github.com/google-gemini/gemini-cli/pull/24057)
- feat(core): implement tool-based topic grouping (Chapters) by @Abhijit-2592 in
[#23150](https://github.com/google-gemini/gemini-cli/pull/23150)
- feat(cli): support 'tab to queue' for messages while generating by @gundermanc
in [#24052](https://github.com/google-gemini/gemini-cli/pull/24052)
- feat(core): agnostic background task UI with CompletionBehavior by
@adamfweidman in
[#22740](https://github.com/google-gemini/gemini-cli/pull/22740)
- UX for topic narration tool by @gundermanc in
[#24079](https://github.com/google-gemini/gemini-cli/pull/24079)
- fix: shellcheck warnings in scripts by @scidomino in
[#24035](https://github.com/google-gemini/gemini-cli/pull/24035)
- test(evals): add comprehensive subagent delegation evaluations by @abhipatel12
in [#24132](https://github.com/google-gemini/gemini-cli/pull/24132)
- fix(a2a-server): prioritize ADC before evaluating headless constraints for
auth initialization by @spencer426 in
[#23614](https://github.com/google-gemini/gemini-cli/pull/23614)
- Text can be added after /plan command by @rambleraptor in
[#22833](https://github.com/google-gemini/gemini-cli/pull/22833)
- fix(cli): resolve missing F12 logs via global console store by @scidomino in
[#24235](https://github.com/google-gemini/gemini-cli/pull/24235)
- fix broken tests by @scidomino in
[#24279](https://github.com/google-gemini/gemini-cli/pull/24279)
- fix(evals): add update_topic behavioral eval by @gundermanc in
[#24223](https://github.com/google-gemini/gemini-cli/pull/24223)
- feat(core): Unified Context Management and Tool Distillation. by @joshualitt
in [#24157](https://github.com/google-gemini/gemini-cli/pull/24157)
- Default enable narration for the team. by @gundermanc in
[#24224](https://github.com/google-gemini/gemini-cli/pull/24224)
- fix(core): ensure default agents provide tools and use model-specific schemas
by @abhipatel12 in
[#21917](https://github.com/google-gemini/gemini-cli/pull/21917)
- merge duplicate imports packages/cli/src subtask3 by @Nixxx19 in
[#22056](https://github.com/google-gemini/gemini-cli/pull/22056)
- fix(hooks): fix BeforeAgent/AfterAgent inconsistencies (#18514) by @krishdef7
in [#21383](https://github.com/google-gemini/gemini-cli/pull/21383)
- feat(core): implement SandboxManager interface and config schema by @galz10 in
[#21774](https://github.com/google-gemini/gemini-cli/pull/21774)
- docs: document npm deprecation warnings as safe to ignore by @h30s in
[#20692](https://github.com/google-gemini/gemini-cli/pull/20692)
- fix: remove status/need-triage from maintainer-only issues by @SandyTao520 in
[#22044](https://github.com/google-gemini/gemini-cli/pull/22044)
- fix(core): propagate subagent context to policy engine by @NTaylorMullen in
[#22086](https://github.com/google-gemini/gemini-cli/pull/22086)
- fix(cli): resolve skill uninstall failure when skill name is updated by
@NTaylorMullen in
[#22085](https://github.com/google-gemini/gemini-cli/pull/22085)
- docs(plan): clarify interactive plan editing with Ctrl+X by @Adib234 in
[#22076](https://github.com/google-gemini/gemini-cli/pull/22076)
- fix(policy): ensure user policies are loaded when policyPaths is empty by
@NTaylorMullen in
[#22090](https://github.com/google-gemini/gemini-cli/pull/22090)
- Docs: Add documentation for model steering (experimental). by @jkcinouye in
[#21154](https://github.com/google-gemini/gemini-cli/pull/21154)
- Add issue for automated changelogs by @g-samroberts in
[#21912](https://github.com/google-gemini/gemini-cli/pull/21912)
- fix(core): secure argsPattern and revert WEB_FETCH_TOOL_NAME escalation by
@spencer426 in
[#22104](https://github.com/google-gemini/gemini-cli/pull/22104)
- feat(core): differentiate User-Agent for a2a-server and ACP clients by
@bdmorgan in [#22059](https://github.com/google-gemini/gemini-cli/pull/22059)
- refactor(core): extract ExecutionLifecycleService for tool backgrounding by
@adamfweidman in
[#21717](https://github.com/google-gemini/gemini-cli/pull/21717)
- feat: Display pending and confirming tool calls by @sripasg in
[#22106](https://github.com/google-gemini/gemini-cli/pull/22106)
- feat(browser): implement input blocker overlay during automation by
@kunal-10-cloud in
[#21132](https://github.com/google-gemini/gemini-cli/pull/21132)
- fix: register themes on extension load not start by @jackwotherspoon in
[#22148](https://github.com/google-gemini/gemini-cli/pull/22148)
- feat(ui): Do not show Ultra users /upgrade hint (#22154) by @sehoon38 in
[#22156](https://github.com/google-gemini/gemini-cli/pull/22156)
- chore: remove unnecessary log for themes by @jackwotherspoon in
[#22165](https://github.com/google-gemini/gemini-cli/pull/22165)
- fix(core): resolve MCP tool FQN validation, schema export, and wildcards in
subagents by @abhipatel12 in
[#22069](https://github.com/google-gemini/gemini-cli/pull/22069)
- fix(cli): validate --model argument at startup by @JaisalJain in
[#21393](https://github.com/google-gemini/gemini-cli/pull/21393)
- fix(core): handle policy ALLOW for exit_plan_mode by @backnotprop in
[#21802](https://github.com/google-gemini/gemini-cli/pull/21802)
- feat(telemetry): add Clearcut instrumentation for AI credits billing events by
@gsquared94 in
[#22153](https://github.com/google-gemini/gemini-cli/pull/22153)
- feat(core): add google credentials provider for remote agents by @adamfweidman
in [#21024](https://github.com/google-gemini/gemini-cli/pull/21024)
- test(cli): add integration test for node deprecation warnings by @Nixxx19 in
[#20215](https://github.com/google-gemini/gemini-cli/pull/20215)
- feat(cli): allow safe tools to execute concurrently while agent is busy by
@spencer426 in
[#21988](https://github.com/google-gemini/gemini-cli/pull/21988)
- feat(core): implement model-driven parallel tool scheduler by @abhipatel12 in
[#21933](https://github.com/google-gemini/gemini-cli/pull/21933)
- update vulnerable deps by @scidomino in
[#22180](https://github.com/google-gemini/gemini-cli/pull/22180)
- fix(core): fix startup stats to use int values for timestamps and durations by
@yunaseoul in [#22201](https://github.com/google-gemini/gemini-cli/pull/22201)
- fix(core): prevent duplicate tool schemas for instantiated tools by
@abhipatel12 in
[#22204](https://github.com/google-gemini/gemini-cli/pull/22204)
- fix(core): add proxy routing support for remote A2A subagents by @adamfweidman
in [#22199](https://github.com/google-gemini/gemini-cli/pull/22199)
- fix(core/ide): add Antigravity CLI fallbacks by @apfine in
[#22030](https://github.com/google-gemini/gemini-cli/pull/22030)
- fix(browser): fix duplicate function declaration error in browser agent by
@gsquared94 in
[#22207](https://github.com/google-gemini/gemini-cli/pull/22207)
- feat(core): implement Stage 1 improvements for webfetch tool by @aishaneeshah
in [#21313](https://github.com/google-gemini/gemini-cli/pull/21313)
- Changelog for v0.34.0-preview.1 by @gemini-cli-robot in
[#22194](https://github.com/google-gemini/gemini-cli/pull/22194)
- perf(cli): enable code splitting and deferred UI loading by @sehoon38 in
[#22117](https://github.com/google-gemini/gemini-cli/pull/22117)
- fix: remove unused img.png from project root by @SandyTao520 in
[#22222](https://github.com/google-gemini/gemini-cli/pull/22222)
- docs(local model routing): add docs on how to use Gemma for local model
routing by @douglas-reid in
[#21365](https://github.com/google-gemini/gemini-cli/pull/21365)
- feat(a2a): enable native gRPC support and protocol routing by @alisa-alisa in
[#21403](https://github.com/google-gemini/gemini-cli/pull/21403)
- fix(cli): escape @ symbols on paste to prevent unintended file expansion by
@krishdef7 in [#21239](https://github.com/google-gemini/gemini-cli/pull/21239)
- feat(core): add trajectoryId to ConversationOffered telemetry by @yunaseoul in
[#22214](https://github.com/google-gemini/gemini-cli/pull/22214)
- docs: clarify that tools.core is an allowlist for ALL built-in tools by
@hobostay in [#18813](https://github.com/google-gemini/gemini-cli/pull/18813)
- docs(plan): document hooks with plan mode by @ruomengz in
[#22197](https://github.com/google-gemini/gemini-cli/pull/22197)
- Changelog for v0.33.1 by @gemini-cli-robot in
[#22235](https://github.com/google-gemini/gemini-cli/pull/22235)
- build(ci): fix false positive evals trigger on merge commits by @gundermanc in
[#22237](https://github.com/google-gemini/gemini-cli/pull/22237)
- fix(core): explicitly pass messageBus to policy engine for MCP tool saves by
@abhipatel12 in
[#22255](https://github.com/google-gemini/gemini-cli/pull/22255)
- feat(core): Fully migrate packages/core to AgentLoopContext. by @joshualitt in
[#22115](https://github.com/google-gemini/gemini-cli/pull/22115)
- feat(core): increase sub-agent turn and time limits by @bdmorgan in
[#22196](https://github.com/google-gemini/gemini-cli/pull/22196)
- feat(core): instrument file system tools for JIT context discovery by
@SandyTao520 in
[#22082](https://github.com/google-gemini/gemini-cli/pull/22082)
- refactor(ui): extract pure session browser utilities by @abhipatel12 in
[#22256](https://github.com/google-gemini/gemini-cli/pull/22256)
- fix(plan): Fix AskUser evals by @Adib234 in
[#22074](https://github.com/google-gemini/gemini-cli/pull/22074)
- fix(settings): prevent j/k navigation keys from intercepting edit buffer input
by @student-ankitpandit in
[#21865](https://github.com/google-gemini/gemini-cli/pull/21865)
- feat(skills): improve async-pr-review workflow and logging by @mattKorwel in
[#21790](https://github.com/google-gemini/gemini-cli/pull/21790)
- refactor(cli): consolidate getErrorMessage utility to core by @scidomino in
[#22190](https://github.com/google-gemini/gemini-cli/pull/22190)
- fix(core): show descriptive error messages when saving settings fails by
@afarber in [#18095](https://github.com/google-gemini/gemini-cli/pull/18095)
- docs(core): add authentication guide for remote subagents by @adamfweidman in
[#22178](https://github.com/google-gemini/gemini-cli/pull/22178)
- docs: overhaul subagents documentation and add /agents command by @abhipatel12
in [#22345](https://github.com/google-gemini/gemini-cli/pull/22345)
- refactor(ui): extract SessionBrowser static ui components by @abhipatel12 in
[#22348](https://github.com/google-gemini/gemini-cli/pull/22348)
- test: add Object.create context regression test and tool confirmation
integration test by @gsquared94 in
[#22356](https://github.com/google-gemini/gemini-cli/pull/22356)
- feat(tracker): return TodoList display for tracker tools by @anj-s in
[#22060](https://github.com/google-gemini/gemini-cli/pull/22060)
- feat(agent): add allowed domain restrictions for browser agent by
[#24268](https://github.com/google-gemini/gemini-cli/pull/24268)
- feat(cli): show Flash Lite Preview model regardless of user tier by @sehoon38
in [#23904](https://github.com/google-gemini/gemini-cli/pull/23904)
- feat(cli): implement compact tool output by @jwhelangoog in
[#20974](https://github.com/google-gemini/gemini-cli/pull/20974)
- Add security settings for tool sandboxing by @galz10 in
[#23923](https://github.com/google-gemini/gemini-cli/pull/23923)
- chore(test-utils): switch integration tests to use PREVIEW_GEMINI_MODEL by
@sehoon38 in [#24276](https://github.com/google-gemini/gemini-cli/pull/24276)
- feat(core): enable topic update narration for legacy models by @Abhijit-2592
in [#24241](https://github.com/google-gemini/gemini-cli/pull/24241)
- feat(core): add project-level memory scope to save_memory tool by @SandyTao520
in [#24161](https://github.com/google-gemini/gemini-cli/pull/24161)
- test(integration): fix plan mode write denial test false positive by @sehoon38
in [#24299](https://github.com/google-gemini/gemini-cli/pull/24299)
- feat(plan): support `Plan` mode in untrusted folders by @Adib234 in
[#17586](https://github.com/google-gemini/gemini-cli/pull/17586)
- fix(core): enable mid-stream retries for all models and re-enable compression
test by @sehoon38 in
[#24302](https://github.com/google-gemini/gemini-cli/pull/24302)
- Changelog for v0.36.0-preview.6 by @gemini-cli-robot in
[#24082](https://github.com/google-gemini/gemini-cli/pull/24082)
- Changelog for v0.35.3 by @gemini-cli-robot in
[#24083](https://github.com/google-gemini/gemini-cli/pull/24083)
- feat(cli): add auth info to footer by @sehoon38 in
[#24042](https://github.com/google-gemini/gemini-cli/pull/24042)
- fix(browser): reset action counter for each agent session and let it ignore
internal actions by @cynthialong0-0 in
[#24228](https://github.com/google-gemini/gemini-cli/pull/24228)
- feat(plan): promote planning feature to stable by @ruomengz in
[#24282](https://github.com/google-gemini/gemini-cli/pull/24282)
- fix(browser): terminate subagent immediately on domain restriction violations
by @gsquared94 in
[#24313](https://github.com/google-gemini/gemini-cli/pull/24313)
- feat(cli): add UI to update extensions by @ruomengz in
[#23682](https://github.com/google-gemini/gemini-cli/pull/23682)
- Fix(browser): terminate immediately for "browser is already running" error by
@cynthialong0-0 in
[#21775](https://github.com/google-gemini/gemini-cli/pull/21775)
- chore/release: bump version to 0.35.0-nightly.20260313.bb060d7a9 by
@gemini-cli-robot in
[#22251](https://github.com/google-gemini/gemini-cli/pull/22251)
- Move keychain fallback to keychain service by @chrstnb in
[#22332](https://github.com/google-gemini/gemini-cli/pull/22332)
- feat(core): integrate SandboxManager to sandbox all process-spawning tools by
@galz10 in [#22231](https://github.com/google-gemini/gemini-cli/pull/22231)
- fix(cli): support CJK input and full Unicode scalar values in terminal
protocols by @scidomino in
[#22353](https://github.com/google-gemini/gemini-cli/pull/22353)
- Promote stable tests. by @gundermanc in
[#22253](https://github.com/google-gemini/gemini-cli/pull/22253)
- feat(tracker): add tracker policy by @anj-s in
[#22379](https://github.com/google-gemini/gemini-cli/pull/22379)
- feat(security): add disableAlwaysAllow setting to disable auto-approvals by
@galz10 in [#21941](https://github.com/google-gemini/gemini-cli/pull/21941)
- Revert "fix(cli): validate --model argument at startup" by @sehoon38 in
[#22378](https://github.com/google-gemini/gemini-cli/pull/22378)
- fix(mcp): handle equivalent root resource URLs in OAuth validation by @galz10
in [#20231](https://github.com/google-gemini/gemini-cli/pull/20231)
- fix(core): use session-specific temp directory for task tracker by @anj-s in
[#22382](https://github.com/google-gemini/gemini-cli/pull/22382)
- Fix issue where config was undefined. by @gundermanc in
[#22397](https://github.com/google-gemini/gemini-cli/pull/22397)
- fix(core): deduplicate project memory when JIT context is enabled by
@SandyTao520 in
[#22234](https://github.com/google-gemini/gemini-cli/pull/22234)
- feat(prompts): implement Topic-Action-Summary model for verbosity reduction by
@Abhijit-2592 in
[#21503](https://github.com/google-gemini/gemini-cli/pull/21503)
- fix(core): fix manual deletion of subagent histories by @abhipatel12 in
[#22407](https://github.com/google-gemini/gemini-cli/pull/22407)
- Add registry var by @kevinjwang1 in
[#22224](https://github.com/google-gemini/gemini-cli/pull/22224)
- Add ModelDefinitions to ModelConfigService by @kevinjwang1 in
[#22302](https://github.com/google-gemini/gemini-cli/pull/22302)
- fix(cli): improve command conflict handling for skills by @NTaylorMullen in
[#21942](https://github.com/google-gemini/gemini-cli/pull/21942)
- fix(core): merge user settings with extension-provided MCP servers by
[#24233](https://github.com/google-gemini/gemini-cli/pull/24233)
- docs: Add 'plan' option to approval mode in CLI reference by @YifanRuan in
[#24134](https://github.com/google-gemini/gemini-cli/pull/24134)
- fix(core): batch macOS seatbelt rules into a profile file to prevent ARG_MAX
errors by @ehedlund in
[#24255](https://github.com/google-gemini/gemini-cli/pull/24255)
- fix(core): fix race condition between browser agent and main closing process
by @cynthialong0-0 in
[#24340](https://github.com/google-gemini/gemini-cli/pull/24340)
- perf(build): optimize build scripts for parallel execution and remove
redundant checks by @sehoon38 in
[#24307](https://github.com/google-gemini/gemini-cli/pull/24307)
- ci: install bubblewrap on Linux for release workflows by @ehedlund in
[#24347](https://github.com/google-gemini/gemini-cli/pull/24347)
- chore(release): allow bundling for all builds, including stable by @sehoon38
in [#24305](https://github.com/google-gemini/gemini-cli/pull/24305)
- Revert "Add security settings for tool sandboxing" by @jerop in
[#24357](https://github.com/google-gemini/gemini-cli/pull/24357)
- docs: update subagents docs to not be experimental by @abhipatel12 in
[#24343](https://github.com/google-gemini/gemini-cli/pull/24343)
- fix(core): implement **read and **write commands in sandbox managers by
@galz10 in [#24283](https://github.com/google-gemini/gemini-cli/pull/24283)
- don't try to remove tags in dry run by @scidomino in
[#24356](https://github.com/google-gemini/gemini-cli/pull/24356)
- fix(config): disable JIT context loading by default by @SandyTao520 in
[#24364](https://github.com/google-gemini/gemini-cli/pull/24364)
- test(sandbox): add integration test for dynamic permission expansion by
@galz10 in [#24359](https://github.com/google-gemini/gemini-cli/pull/24359)
- docs(policy): remove unsupported mcpName wildcard edge case by @abhipatel12 in
[#24133](https://github.com/google-gemini/gemini-cli/pull/24133)
- docs: fix broken GEMINI.md link in CONTRIBUTING.md by @Panchal-Tirth in
[#24182](https://github.com/google-gemini/gemini-cli/pull/24182)
- feat(core): infrastructure for event-driven subagent history by @abhipatel12
in [#23914](https://github.com/google-gemini/gemini-cli/pull/23914)
- fix(core): resolve Plan Mode deadlock during plan file creation due to sandbox
restrictions by @DavidAPierce in
[#24047](https://github.com/google-gemini/gemini-cli/pull/24047)
- fix(core): fix browser agent UX issues and improve E2E test reliability by
@gsquared94 in
[#24312](https://github.com/google-gemini/gemini-cli/pull/24312)
- fix(ui): wrap topic and intent fields in TopicMessage by @jwhelangoog in
[#24386](https://github.com/google-gemini/gemini-cli/pull/24386)
- refactor(core): Centralize context management logic into src/context by
@joshualitt in
[#24380](https://github.com/google-gemini/gemini-cli/pull/24380)
- fix(core): pin AuthType.GATEWAY to use Gemini 3.1 Pro/Flash Lite by default by
@sripasg in [#24375](https://github.com/google-gemini/gemini-cli/pull/24375)
- feat(ui): add Tokyo Night theme by @danrneal in
[#24054](https://github.com/google-gemini/gemini-cli/pull/24054)
- fix(cli): refactor test config loading and mock debugLogger in test-setup by
@mattKorwel in
[#24389](https://github.com/google-gemini/gemini-cli/pull/24389)
- Set memoryManager to false in settings.json by @mattKorwel in
[#24393](https://github.com/google-gemini/gemini-cli/pull/24393)
- ink 6.6.3 by @jacob314 in
[#24372](https://github.com/google-gemini/gemini-cli/pull/24372)
- fix(core): resolve subagent chat recording gaps and directory inheritance by
@abhipatel12 in
[#22484](https://github.com/google-gemini/gemini-cli/pull/22484)
- fix(core): skip discovery for incomplete MCP configs and resolve merge race
condition by @abhipatel12 in
[#22494](https://github.com/google-gemini/gemini-cli/pull/22494)
- fix(automation): harden stale PR closer permissions and maintainer detection
by @bdmorgan in
[#22558](https://github.com/google-gemini/gemini-cli/pull/22558)
- fix(automation): evaluate staleness before checking protected labels by
@bdmorgan in [#22561](https://github.com/google-gemini/gemini-cli/pull/22561)
- feat(agent): replace the runtime npx for browser agent chrome devtool mcp with
pre-built bundle by @cynthialong0-0 in
[#22213](https://github.com/google-gemini/gemini-cli/pull/22213)
- perf: optimize TrackerService dependency checks by @anj-s in
[#22384](https://github.com/google-gemini/gemini-cli/pull/22384)
- docs(policy): remove trailing space from commandPrefix examples by @kawasin73
in [#22264](https://github.com/google-gemini/gemini-cli/pull/22264)
- fix(a2a-server): resolve unsafe assignment lint errors by @ehedlund in
[#22661](https://github.com/google-gemini/gemini-cli/pull/22661)
- fix: Adjust ToolGroupMessage filtering to hide Confirming and show Canceled
tool calls. by @sripasg in
[#22230](https://github.com/google-gemini/gemini-cli/pull/22230)
- Disallow Object.create() and reflect. by @gundermanc in
[#22408](https://github.com/google-gemini/gemini-cli/pull/22408)
- Guard pro model usage by @sehoon38 in
[#22665](https://github.com/google-gemini/gemini-cli/pull/22665)
- refactor(core): Creates AgentSession abstraction for consolidated agent
interface. by @mbleigh in
[#22270](https://github.com/google-gemini/gemini-cli/pull/22270)
- docs(changelog): remove internal commands from release notes by
@jackwotherspoon in
[#22529](https://github.com/google-gemini/gemini-cli/pull/22529)
- feat: enable subagents by @abhipatel12 in
[#22386](https://github.com/google-gemini/gemini-cli/pull/22386)
- feat(extensions): implement cryptographic integrity verification for extension
updates by @ehedlund in
[#21772](https://github.com/google-gemini/gemini-cli/pull/21772)
- feat(tracker): polish UI sorting and formatting by @anj-s in
[#22437](https://github.com/google-gemini/gemini-cli/pull/22437)
- Changelog for v0.34.0-preview.2 by @gemini-cli-robot in
[#22220](https://github.com/google-gemini/gemini-cli/pull/22220)
- fix(core): fix three JIT context bugs in read_file, read_many_files, and
memoryDiscovery by @SandyTao520 in
[#22679](https://github.com/google-gemini/gemini-cli/pull/22679)
- refactor(core): introduce InjectionService with source-aware injection and
backend-native background completions by @adamfweidman in
[#22544](https://github.com/google-gemini/gemini-cli/pull/22544)
- Linux sandbox bubblewrap by @DavidAPierce in
[#22680](https://github.com/google-gemini/gemini-cli/pull/22680)
- feat(core): increase thought signature retry resilience by @bdmorgan in
[#22202](https://github.com/google-gemini/gemini-cli/pull/22202)
- feat(core): implement Stage 2 security and consistency improvements for
web_fetch by @aishaneeshah in
[#22217](https://github.com/google-gemini/gemini-cli/pull/22217)
- refactor(core): replace positional execute params with ExecuteOptions bag by
@adamfweidman in
[#22674](https://github.com/google-gemini/gemini-cli/pull/22674)
- feat(config): enable JIT context loading by default by @SandyTao520 in
[#22736](https://github.com/google-gemini/gemini-cli/pull/22736)
- fix(config): ensure discoveryMaxDirs is passed to global config during
initialization by @kevin-ramdass in
[#22744](https://github.com/google-gemini/gemini-cli/pull/22744)
- fix(plan): allowlist get_internal_docs in Plan Mode by @Adib234 in
[#22668](https://github.com/google-gemini/gemini-cli/pull/22668)
- Changelog for v0.34.0-preview.3 by @gemini-cli-robot in
[#22393](https://github.com/google-gemini/gemini-cli/pull/22393)
- feat(core): add foundation for subagent tool isolation by @akh64bit in
[#22708](https://github.com/google-gemini/gemini-cli/pull/22708)
- fix(core): handle surrogate pairs in truncateString by @sehoon38 in
[#22754](https://github.com/google-gemini/gemini-cli/pull/22754)
- fix(cli): override j/k navigation in settings dialog to fix search input
conflict by @sehoon38 in
[#22800](https://github.com/google-gemini/gemini-cli/pull/22800)
- feat(plan): add 'All the above' option to multi-select AskUser questions by
@Adib234 in [#22365](https://github.com/google-gemini/gemini-cli/pull/22365)
- docs: distribute package-specific GEMINI.md context to each package by
@SandyTao520 in
[#22734](https://github.com/google-gemini/gemini-cli/pull/22734)
- fix(cli): clean up stale pasted placeholder metadata after word/line deletions
by @Jomak-x in
[#20375](https://github.com/google-gemini/gemini-cli/pull/20375)
- refactor(core): align JIT memory placement with tiered context model by
@SandyTao520 in
[#22766](https://github.com/google-gemini/gemini-cli/pull/22766)
- Linux sandbox seccomp by @DavidAPierce in
[#22815](https://github.com/google-gemini/gemini-cli/pull/22815)
- fix(patch): cherry-pick 4e5dfd0 to release/v0.35.0-preview.1-pr-23074 to patch
version v0.35.0-preview.1 and create version 0.35.0-preview.2 by
[#24368](https://github.com/google-gemini/gemini-cli/pull/24368)
- fix(cli): cap shell output at 10 MB to prevent RangeError crash by @ProthamD
in [#24168](https://github.com/google-gemini/gemini-cli/pull/24168)
- feat(plan): conditionally add enter/exit plan mode tools based on current mode
by @ruomengz in
[#24378](https://github.com/google-gemini/gemini-cli/pull/24378)
- feat(core): prioritize discussion before formal plan approval by @jerop in
[#24423](https://github.com/google-gemini/gemini-cli/pull/24423)
- fix(ui): add accelerated scrolling on alternate buffer mode by @devr0306 in
[#23940](https://github.com/google-gemini/gemini-cli/pull/23940)
- feat(core): populate sandbox forbidden paths with project ignore file contents
by @ehedlund in
[#24038](https://github.com/google-gemini/gemini-cli/pull/24038)
- fix(core): ensure blue border overlay and input blocker to act correctly
depending on browser agent activities by @cynthialong0-0 in
[#24385](https://github.com/google-gemini/gemini-cli/pull/24385)
- fix(ui): removed additional vertical padding for tables by @devr0306 in
[#24381](https://github.com/google-gemini/gemini-cli/pull/24381)
- fix(build): upload full bundle directory archive to GitHub releases by
@sehoon38 in [#24403](https://github.com/google-gemini/gemini-cli/pull/24403)
- fix(build): wire bundle:browser-mcp into bundle pipeline by @gsquared94 in
[#24424](https://github.com/google-gemini/gemini-cli/pull/24424)
- feat(browser): add sandbox-aware browser agent initialization by @gsquared94
in [#24419](https://github.com/google-gemini/gemini-cli/pull/24419)
- feat(core): enhance tracker task schemas for detailed titles and descriptions
by @anj-s in [#23902](https://github.com/google-gemini/gemini-cli/pull/23902)
- refactor(core): Unified context management settings schema by @joshualitt in
[#24391](https://github.com/google-gemini/gemini-cli/pull/24391)
- feat(core): update browser agent prompt to check open pages first when
bringing up by @cynthialong0-0 in
[#24431](https://github.com/google-gemini/gemini-cli/pull/24431)
- fix(acp) refactor(core,cli): centralize model discovery logic in
ModelConfigService by @sripasg in
[#24392](https://github.com/google-gemini/gemini-cli/pull/24392)
- Changelog for v0.36.0-preview.7 by @gemini-cli-robot in
[#24346](https://github.com/google-gemini/gemini-cli/pull/24346)
- fix: update task tracker storage location in system prompt by @anj-s in
[#24034](https://github.com/google-gemini/gemini-cli/pull/24034)
- feat(browser): supersede stale snapshots to reclaim context-window tokens by
@gsquared94 in
[#24440](https://github.com/google-gemini/gemini-cli/pull/24440)
- docs(core): add subagent tool isolation draft doc by @akh64bit in
[#23275](https://github.com/google-gemini/gemini-cli/pull/23275)
- fix(patch): cherry-pick 64c928f to release/v0.37.0-preview.0-pr-23257 to patch
version v0.37.0-preview.0 and create version 0.37.0-preview.1 by
@gemini-cli-robot in
[#23134](https://github.com/google-gemini/gemini-cli/pull/23134)
- fix(patch): cherry-pick daf3691 to release/v0.35.0-preview.2-pr-23558 to patch
version v0.35.0-preview.2 and create version 0.35.0-preview.3 by
[#24561](https://github.com/google-gemini/gemini-cli/pull/24561)
- fix(patch): cherry-pick cb7f7d6 to release/v0.37.0-preview.1-pr-24342 to patch
version v0.37.0-preview.1 and create version 0.37.0-preview.2 by
@gemini-cli-robot in
[#23565](https://github.com/google-gemini/gemini-cli/pull/23565)
- fix(patch): cherry-pick b2d6dc4 to release/v0.35.0-preview.4-pr-23546
[CONFLICTS] by @gemini-cli-robot in
[#23585](https://github.com/google-gemini/gemini-cli/pull/23585)
[#24842](https://github.com/google-gemini/gemini-cli/pull/24842)
**Full Changelog**:
https://github.com/google-gemini/gemini-cli/compare/v0.34.0...v0.35.3
https://github.com/google-gemini/gemini-cli/compare/v0.36.0...v0.37.0
+245 -370
View File
@@ -1,6 +1,6 @@
# Preview release: v0.36.0-preview.8
# Preview release: v0.38.0-preview.0
Released: April 01, 2026
Released: April 08, 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,381 +13,256 @@ npm install -g @google/gemini-cli@preview
## Highlights
- **Subagent Architecture Enhancements:** Significant updates to subagents,
including local execution, tool isolation, multi-registry discovery, dynamic
tool filtering, and JIT context injection.
- **Enhanced Security & Sandboxing:** Implemented strict macOS sandboxing using
Seatbelt allowlist, native Windows sandboxing, and support for
"Write-Protected" governance files.
- **Agent Context & State Management:** Introduced task tracker protocol
integration, 'blocked' statuses for tasks/todos, and `AgentSession` for
improved state management and replay semantics.
- **Browser & ACP Capabilities:** Added privacy consent for the browser agent,
sensitive action controls, improved API token usage metadata, and gateway auth
support via ACP.
- **CLI & UX Improvements:** Implemented a refreshed Composer layout, expanded
terminal fallback warnings, dynamic model resolution, and Git worktree support
for isolated parallel sessions.
- **Context Management:** Introduced a Context Compression Service to optimize
context window usage and landed a background memory service for skill
extraction.
- **Enhanced Security:** Implemented context-aware persistent policy approvals
for smarter tool permissions and enabled `web_fetch` in plan mode with user
confirmation.
- **Workflow Monitoring:** Added background process monitoring and inspection
tools for better visibility into long-running tasks.
- **UI/UX Refinements:** Enhanced the tool confirmation UI, selection layout,
and added support for selective topic expansion and click-to-expand.
- **Core Stability:** Improved sandbox reliability on Linux and Windows,
resolved shebang compatibility issues, and fixed various crashes in the CLI
and core services.
## What's Changed
- fix(patch): cherry-pick 765fb67 to release/v0.36.0-preview.5-pr-24055 to patch
version v0.36.0-preview.5 and create version 0.36.0-preview.6 by
@gemini-cli-robot in
[#24061](https://github.com/google-gemini/gemini-cli/pull/24061)
- fix(a2a-server): A2A server should execute ask policies in interactive mode by
@kschaab in [#23831](https://github.com/google-gemini/gemini-cli/pull/23831)
- docs(core): document agent_card_json string literal options for remote agents
- fix(cli): refresh slash command list after /skills reload by @NTaylorMullen in
[#24454](https://github.com/google-gemini/gemini-cli/pull/24454)
- Update README.md for links. by @g-samroberts in
[#22759](https://github.com/google-gemini/gemini-cli/pull/22759)
- fix(core): ensure complete_task tool calls are recorded in chat history by
@abhipatel12 in
[#24437](https://github.com/google-gemini/gemini-cli/pull/24437)
- feat(policy): explicitly allow web_fetch in plan mode with ask_user by
@Adib234 in [#24456](https://github.com/google-gemini/gemini-cli/pull/24456)
- fix(core): refactor linux sandbox to fix ARG_MAX crashes by @ehedlund in
[#24286](https://github.com/google-gemini/gemini-cli/pull/24286)
- feat(config): add experimental.adk.agentSessionNoninteractiveEnabled setting
by @adamfweidman in
[#23797](https://github.com/google-gemini/gemini-cli/pull/23797)
- feat(core): support inline agentCardJson for remote agents by @adamfweidman in
[#23743](https://github.com/google-gemini/gemini-cli/pull/23743)
- fix(patch): cherry-pick 055ff92 to release/v0.36.0-preview.0-pr-23672 to patch
version v0.36.0-preview.0 and create version 0.36.0-preview.1 by
@gemini-cli-robot in
[#23723](https://github.com/google-gemini/gemini-cli/pull/23723)
- Changelog for v0.33.2 by @gemini-cli-robot in
[#22730](https://github.com/google-gemini/gemini-cli/pull/22730)
- feat(core): multi-registry architecture and tool filtering for subagents by
@akh64bit in [#22712](https://github.com/google-gemini/gemini-cli/pull/22712)
- Changelog for v0.34.0-preview.4 by @gemini-cli-robot in
[#22752](https://github.com/google-gemini/gemini-cli/pull/22752)
- fix(devtools): use theme-aware text colors for console warnings and errors by
@SandyTao520 in
[#22181](https://github.com/google-gemini/gemini-cli/pull/22181)
- Add support for dynamic model Resolution to ModelConfigService by @kevinjwang1
in [#22578](https://github.com/google-gemini/gemini-cli/pull/22578)
- chore(release): bump version to 0.36.0-nightly.20260317.2f90b4653 by
@gemini-cli-robot in
[#22858](https://github.com/google-gemini/gemini-cli/pull/22858)
- fix(cli): use active sessionId in useLogger and improve resume robustness by
@mattKorwel in
[#22606](https://github.com/google-gemini/gemini-cli/pull/22606)
- fix(cli): expand tilde in policy paths from settings.json by @abhipatel12 in
[#22772](https://github.com/google-gemini/gemini-cli/pull/22772)
- fix(core): add actionable warnings for terminal fallbacks (#14426) by
@spencer426 in
[#22211](https://github.com/google-gemini/gemini-cli/pull/22211)
- feat(tracker): integrate task tracker protocol into core system prompt by
@anj-s in [#22442](https://github.com/google-gemini/gemini-cli/pull/22442)
- chore: add posttest build hooks and fix missing dependencies by @NTaylorMullen
in [#22865](https://github.com/google-gemini/gemini-cli/pull/22865)
- feat(a2a): add agent acknowledgment command and enhance registry discovery by
[#24439](https://github.com/google-gemini/gemini-cli/pull/24439)
- Changelog for v0.36.0-preview.8 by @gemini-cli-robot in
[#24453](https://github.com/google-gemini/gemini-cli/pull/24453)
- feat(cli): change default loadingPhrases to 'off' to hide tips by @keithguerin
in [#24342](https://github.com/google-gemini/gemini-cli/pull/24342)
- fix(cli): ensure agent stops when all declinable tools are cancelled by
@NTaylorMullen in
[#24479](https://github.com/google-gemini/gemini-cli/pull/24479)
- fix(core): enhance sandbox usability and fix build error by @galz10 in
[#24460](https://github.com/google-gemini/gemini-cli/pull/24460)
- Terminal Serializer Optimization by @jacob314 in
[#24485](https://github.com/google-gemini/gemini-cli/pull/24485)
- Auto configure memory. by @jacob314 in
[#24474](https://github.com/google-gemini/gemini-cli/pull/24474)
- Unused error variables in catch block are not allowed by @alisa-alisa in
[#24487](https://github.com/google-gemini/gemini-cli/pull/24487)
- feat(core): add background memory service for skill extraction by @SandyTao520
in [#24274](https://github.com/google-gemini/gemini-cli/pull/24274)
- feat: implement high-signal PR regression check for evaluations by
@alisa-alisa in
[#22389](https://github.com/google-gemini/gemini-cli/pull/22389)
- fix(cli): automatically add all VSCode workspace folders to Gemini context by
@sakshisemalti in
[#21380](https://github.com/google-gemini/gemini-cli/pull/21380)
- feat: add 'blocked' status to tasks and todos by @anj-s in
[#22735](https://github.com/google-gemini/gemini-cli/pull/22735)
- refactor(cli): remove extra newlines in ShellToolMessage.tsx by @NTaylorMullen
in [#22868](https://github.com/google-gemini/gemini-cli/pull/22868)
- fix(cli): lazily load settings in onModelChange to prevent stale closure data
loss by @KumarADITHYA123 in
[#20403](https://github.com/google-gemini/gemini-cli/pull/20403)
- feat(core): subagent local execution and tool isolation by @akh64bit in
[#22718](https://github.com/google-gemini/gemini-cli/pull/22718)
- fix(cli): resolve subagent grouping and UI state persistence by @abhipatel12
in [#22252](https://github.com/google-gemini/gemini-cli/pull/22252)
- refactor(ui): extract SessionBrowser search and navigation components by
@abhipatel12 in
[#22377](https://github.com/google-gemini/gemini-cli/pull/22377)
- fix: updates Docker image reference for GitHub MCP server by @jhhornn in
[#22938](https://github.com/google-gemini/gemini-cli/pull/22938)
- refactor(cli): group subagent trajectory deletion and use native filesystem
testing by @abhipatel12 in
[#22890](https://github.com/google-gemini/gemini-cli/pull/22890)
- refactor(cli): simplify keypress and mouse providers and update tests by
@scidomino in [#22853](https://github.com/google-gemini/gemini-cli/pull/22853)
- Changelog for v0.34.0 by @gemini-cli-robot in
[#22860](https://github.com/google-gemini/gemini-cli/pull/22860)
- test(cli): simplify createMockSettings calls by @scidomino in
[#22952](https://github.com/google-gemini/gemini-cli/pull/22952)
- feat(ui): format multi-line banner warnings with a bold title by @keithguerin
in [#22955](https://github.com/google-gemini/gemini-cli/pull/22955)
- Docs: Remove references to stale Gemini CLI file structure info by
@g-samroberts in
[#22976](https://github.com/google-gemini/gemini-cli/pull/22976)
- feat(ui): remove write todo list tool from UI tips by @aniruddhaadak80 in
[#22281](https://github.com/google-gemini/gemini-cli/pull/22281)
- Fix issue where subagent thoughts are appended. by @gundermanc in
[#22975](https://github.com/google-gemini/gemini-cli/pull/22975)
- Feat/browser privacy consent by @kunal-10-cloud in
[#21119](https://github.com/google-gemini/gemini-cli/pull/21119)
- fix(core): explicitly map execution context in LocalAgentExecutor by @akh64bit
in [#22949](https://github.com/google-gemini/gemini-cli/pull/22949)
- feat(plan): support plan mode in non-interactive mode by @ruomengz in
[#22670](https://github.com/google-gemini/gemini-cli/pull/22670)
- feat(core): implement strict macOS sandboxing using Seatbelt allowlist by
@ehedlund in [#22832](https://github.com/google-gemini/gemini-cli/pull/22832)
- docs: add additional notes by @abhipatel12 in
[#23008](https://github.com/google-gemini/gemini-cli/pull/23008)
- fix(cli): resolve duplicate footer on tool cancel via ESC (#21743) by
@ruomengz in [#21781](https://github.com/google-gemini/gemini-cli/pull/21781)
- Changelog for v0.35.0-preview.1 by @gemini-cli-robot in
[#23012](https://github.com/google-gemini/gemini-cli/pull/23012)
- fix(ui): fix flickering on small terminal heights by @devr0306 in
[#21416](https://github.com/google-gemini/gemini-cli/pull/21416)
- fix(acp): provide more meta in tool_call_update by @Mervap in
[#22663](https://github.com/google-gemini/gemini-cli/pull/22663)
- docs: add FAQ entry for checking Gemini CLI version by @surajsahani in
[#21271](https://github.com/google-gemini/gemini-cli/pull/21271)
- feat(core): resilient subagent tool rejection with contextual feedback by
@abhipatel12 in
[#22951](https://github.com/google-gemini/gemini-cli/pull/22951)
- fix(cli): correctly handle auto-update for standalone binaries by @bdmorgan in
[#23038](https://github.com/google-gemini/gemini-cli/pull/23038)
- feat(core): add content-utils by @adamfweidman in
[#22984](https://github.com/google-gemini/gemini-cli/pull/22984)
- fix: circumvent genai sdk requirement for api key when using gateway auth via
ACP by @sripasg in
[#23042](https://github.com/google-gemini/gemini-cli/pull/23042)
- fix(core): don't persist browser consent sentinel in non-interactive mode by
@jasonmatthewsuhari in
[#23073](https://github.com/google-gemini/gemini-cli/pull/23073)
- fix(core): narrow browser agent description to prevent stealing URL tasks from
web_fetch by @gsquared94 in
[#23086](https://github.com/google-gemini/gemini-cli/pull/23086)
- feat(cli): Partial threading of AgentLoopContext. by @joshualitt in
[#22978](https://github.com/google-gemini/gemini-cli/pull/22978)
- fix(browser-agent): enable "Allow all server tools" session policy by
@cynthialong0-0 in
[#22343](https://github.com/google-gemini/gemini-cli/pull/22343)
- refactor(cli): integrate real config loading into async test utils by
@scidomino in [#23040](https://github.com/google-gemini/gemini-cli/pull/23040)
- feat(core): inject memory and JIT context into subagents by @abhipatel12 in
[#23032](https://github.com/google-gemini/gemini-cli/pull/23032)
- Fix logging and virtual list. by @jacob314 in
[#23080](https://github.com/google-gemini/gemini-cli/pull/23080)
- feat(core): cap JIT context upward traversal at git root by @SandyTao520 in
[#23074](https://github.com/google-gemini/gemini-cli/pull/23074)
- Docs: Minor style updates from initial docs audit. by @g-samroberts in
[#22872](https://github.com/google-gemini/gemini-cli/pull/22872)
- feat(core): add experimental memory manager agent to replace save_memory tool
by @SandyTao520 in
[#22726](https://github.com/google-gemini/gemini-cli/pull/22726)
- Changelog for v0.35.0-preview.2 by @gemini-cli-robot in
[#23142](https://github.com/google-gemini/gemini-cli/pull/23142)
- Update website issue template for label and title by @g-samroberts in
[#23036](https://github.com/google-gemini/gemini-cli/pull/23036)
- fix: upgrade ACP SDK from 0.12 to 0.16.1 by @sripasg in
[#23132](https://github.com/google-gemini/gemini-cli/pull/23132)
- Update callouts to work on github. by @g-samroberts in
[#22245](https://github.com/google-gemini/gemini-cli/pull/22245)
- feat: ACP: Add token usage metadata to the `send` method's return value by
@sripasg in [#23148](https://github.com/google-gemini/gemini-cli/pull/23148)
- fix(plan): clarify that plan mode policies are combined with normal mode by
@ruomengz in [#23158](https://github.com/google-gemini/gemini-cli/pull/23158)
- Add ModelChain support to ModelConfigService and make ModelDialog dynamic by
@kevinjwang1 in
[#22914](https://github.com/google-gemini/gemini-cli/pull/22914)
- Ensure that copied extensions are writable in the user's local directory by
@kevinjwang1 in
[#23016](https://github.com/google-gemini/gemini-cli/pull/23016)
- feat(core): implement native Windows sandboxing by @mattKorwel in
[#21807](https://github.com/google-gemini/gemini-cli/pull/21807)
- feat(core): add support for admin-forced MCP server installations by
[#23937](https://github.com/google-gemini/gemini-cli/pull/23937)
- Fix shell output display by @jacob314 in
[#24490](https://github.com/google-gemini/gemini-cli/pull/24490)
- fix(ui): resolve unwanted vertical spacing around various tool output
treatments by @jwhelangoog in
[#24449](https://github.com/google-gemini/gemini-cli/pull/24449)
- revert(cli): bring back input box and footer visibility in copy mode by
@sehoon38 in [#24504](https://github.com/google-gemini/gemini-cli/pull/24504)
- fix(cli): prevent crash in AnsiOutputText when handling non-array data by
@sehoon38 in [#24498](https://github.com/google-gemini/gemini-cli/pull/24498)
- feat(cli): support default values for environment variables by @ruomengz in
[#24469](https://github.com/google-gemini/gemini-cli/pull/24469)
- Implement background process monitoring and inspection tools by @cocosheng-g
in [#23799](https://github.com/google-gemini/gemini-cli/pull/23799)
- docs(browser-agent): update stale browser agent documentation by @gsquared94
in [#24463](https://github.com/google-gemini/gemini-cli/pull/24463)
- fix: enable browser_agent in integration tests and add localhost fixture tests
by @gsquared94 in
[#24523](https://github.com/google-gemini/gemini-cli/pull/24523)
- fix(browser): handle computer-use model detection for analyze_screenshot by
@gsquared94 in
[#23163](https://github.com/google-gemini/gemini-cli/pull/23163)
- chore(lint): ignore .gemini directory and recursive node_modules by
@mattKorwel in
[#23211](https://github.com/google-gemini/gemini-cli/pull/23211)
- feat(cli): conditionally exclude ask_user tool in ACP mode by @nmcnamara-eng
in [#23045](https://github.com/google-gemini/gemini-cli/pull/23045)
- feat(core): introduce AgentSession and rename stream events to agent events by
@mbleigh in [#23159](https://github.com/google-gemini/gemini-cli/pull/23159)
- feat(worktree): add Git worktree support for isolated parallel sessions by
@jerop in [#22973](https://github.com/google-gemini/gemini-cli/pull/22973)
- Add support for linking in the extension registry by @kevinjwang1 in
[#23153](https://github.com/google-gemini/gemini-cli/pull/23153)
- feat(extensions): add --skip-settings flag to install command by @Ratish1 in
[#17212](https://github.com/google-gemini/gemini-cli/pull/17212)
- feat(telemetry): track if session is running in a Git worktree by @jerop in
[#23265](https://github.com/google-gemini/gemini-cli/pull/23265)
- refactor(core): use absolute paths in GEMINI.md context markers by
[#24502](https://github.com/google-gemini/gemini-cli/pull/24502)
- feat(core): Land ContextCompressionService by @joshualitt in
[#24483](https://github.com/google-gemini/gemini-cli/pull/24483)
- feat(core): scope subagent workspace directories via AsyncLocalStorage by
@SandyTao520 in
[#23135](https://github.com/google-gemini/gemini-cli/pull/23135)
- fix(core): add sanitization to sub agent thoughts and centralize utilities by
@devr0306 in [#22828](https://github.com/google-gemini/gemini-cli/pull/22828)
- feat(core): refine User-Agent for VS Code traffic (unified format) by
@sehoon38 in [#23256](https://github.com/google-gemini/gemini-cli/pull/23256)
- Fix schema for ModelChains by @kevinjwang1 in
[#23284](https://github.com/google-gemini/gemini-cli/pull/23284)
- test(cli): refactor tests for async render utilities by @scidomino in
[#23252](https://github.com/google-gemini/gemini-cli/pull/23252)
- feat(core): add security prompt for browser agent by @cynthialong0-0 in
[#23241](https://github.com/google-gemini/gemini-cli/pull/23241)
- refactor(ide): replace dynamic undici import with static fetch import by
@cocosheng-g in
[#23268](https://github.com/google-gemini/gemini-cli/pull/23268)
- test(cli): address unresolved feedback from PR #23252 by @scidomino in
[#23303](https://github.com/google-gemini/gemini-cli/pull/23303)
- feat(browser): add sensitive action controls and read-only noise reduction by
@cynthialong0-0 in
[#22867](https://github.com/google-gemini/gemini-cli/pull/22867)
- Disabling failing test while investigating by @alisa-alisa in
[#23311](https://github.com/google-gemini/gemini-cli/pull/23311)
- fix broken extension link in hooks guide by @Indrapal-70 in
[#21728](https://github.com/google-gemini/gemini-cli/pull/21728)
- fix(core): fix agent description indentation by @abhipatel12 in
[#23315](https://github.com/google-gemini/gemini-cli/pull/23315)
- Wrap the text under TOML rule for easier readability in policy-engine.md… by
@CogitationOps in
[#23076](https://github.com/google-gemini/gemini-cli/pull/23076)
- fix(extensions): revert broken extension removal behavior by @ehedlund in
[#23317](https://github.com/google-gemini/gemini-cli/pull/23317)
- feat(core): set up onboarding telemetry by @yunaseoul in
[#23118](https://github.com/google-gemini/gemini-cli/pull/23118)
- Retry evals on API error. by @gundermanc in
[#23322](https://github.com/google-gemini/gemini-cli/pull/23322)
- fix(evals): remove tool restrictions and add compile-time guards by
@SandyTao520 in
[#23312](https://github.com/google-gemini/gemini-cli/pull/23312)
- fix(hooks): support 'ask' decision for BeforeTool hooks by @gundermanc in
[#21146](https://github.com/google-gemini/gemini-cli/pull/21146)
- feat(browser): add warning message for session mode 'existing' by
@cynthialong0-0 in
[#23288](https://github.com/google-gemini/gemini-cli/pull/23288)
- chore(lint): enforce zero warnings and cleanup syntax restrictions by
@alisa-alisa in
[#22902](https://github.com/google-gemini/gemini-cli/pull/22902)
- fix(cli): add Esc instruction to HooksDialog footer by @abhipatel12 in
[#23258](https://github.com/google-gemini/gemini-cli/pull/23258)
- Disallow and suppress misused spread operator. by @gundermanc in
[#23294](https://github.com/google-gemini/gemini-cli/pull/23294)
- fix(core): refine CliHelpAgent description for better delegation by
@abhipatel12 in
[#23310](https://github.com/google-gemini/gemini-cli/pull/23310)
- fix(core): enable global session and persistent approval for web_fetch by
@NTaylorMullen in
[#23295](https://github.com/google-gemini/gemini-cli/pull/23295)
- fix(plan): add state transition override to prevent plan mode freeze by
@Adib234 in [#23020](https://github.com/google-gemini/gemini-cli/pull/23020)
- fix(cli): record skill activation tool calls in chat history by @NTaylorMullen
in [#23203](https://github.com/google-gemini/gemini-cli/pull/23203)
- fix(core): ensure subagent tool updates apply configuration overrides
immediately by @abhipatel12 in
[#23161](https://github.com/google-gemini/gemini-cli/pull/23161)
- fix(cli): resolve flicker at boundaries of list in BaseSelectionList by
[#24445](https://github.com/google-gemini/gemini-cli/pull/24445)
- Update ink version to 6.6.7 by @jacob314 in
[#24514](https://github.com/google-gemini/gemini-cli/pull/24514)
- fix(acp): handle all InvalidStreamError types gracefully in prompt by @sripasg
in [#24540](https://github.com/google-gemini/gemini-cli/pull/24540)
- Fix crash when vim editor is not found in PATH on Windows by
@Nagajyothi-tammisetti in
[#22423](https://github.com/google-gemini/gemini-cli/pull/22423)
- fix(core): move project memory dir under tmp directory by @SandyTao520 in
[#24542](https://github.com/google-gemini/gemini-cli/pull/24542)
- Enable 'Other' option for yesno question type by @ruomengz in
[#24545](https://github.com/google-gemini/gemini-cli/pull/24545)
- fix(cli): clear stale retry/loading state after cancellation (#21096) by
@Aaxhirrr in [#21960](https://github.com/google-gemini/gemini-cli/pull/21960)
- Changelog for v0.37.0-preview.0 by @gemini-cli-robot in
[#24464](https://github.com/google-gemini/gemini-cli/pull/24464)
- feat(core): implement context-aware persistent policy approvals by @jerop in
[#23257](https://github.com/google-gemini/gemini-cli/pull/23257)
- docs: move agent disabling instructions and update remote agent status by
@jackwotherspoon in
[#23298](https://github.com/google-gemini/gemini-cli/pull/23298)
- test(cli): force generic terminal in tests to fix snapshot failures by
@abhipatel12 in
[#23499](https://github.com/google-gemini/gemini-cli/pull/23499)
- Evals: PR Guidance adding workflow by @alisa-alisa in
[#23164](https://github.com/google-gemini/gemini-cli/pull/23164)
- feat(core): refactor SandboxManager to a stateless architecture and introduce
explicit Deny interface by @ehedlund in
[#23141](https://github.com/google-gemini/gemini-cli/pull/23141)
- feat(core): add event-translator and update agent types by @adamfweidman in
[#22985](https://github.com/google-gemini/gemini-cli/pull/22985)
- perf(cli): parallelize and background startup cleanup tasks by @sehoon38 in
[#23545](https://github.com/google-gemini/gemini-cli/pull/23545)
- fix: "allow always" for commands with paths by @scidomino in
[#23558](https://github.com/google-gemini/gemini-cli/pull/23558)
- fix(cli): prevent terminal escape sequences from leaking on exit by
@mattKorwel in
[#22682](https://github.com/google-gemini/gemini-cli/pull/22682)
- feat(cli): implement full "GEMINI CLI" logo for logged-out state by
@keithguerin in
[#22412](https://github.com/google-gemini/gemini-cli/pull/22412)
- fix(plan): reserve minimum height for selection list in AskUserDialog by
@ruomengz in [#23280](https://github.com/google-gemini/gemini-cli/pull/23280)
- fix(core): harden AgentSession replay semantics by @adamfweidman in
[#23548](https://github.com/google-gemini/gemini-cli/pull/23548)
- test(core): migrate hook tests to scheduler by @abhipatel12 in
[#23496](https://github.com/google-gemini/gemini-cli/pull/23496)
- chore(config): disable agents by default by @abhipatel12 in
[#23546](https://github.com/google-gemini/gemini-cli/pull/23546)
- fix(ui): make tool confirmations take up entire terminal height by @devr0306
in [#22366](https://github.com/google-gemini/gemini-cli/pull/22366)
- fix(core): prevent redundant remote agent loading on model switch by
@adamfweidman in
[#23576](https://github.com/google-gemini/gemini-cli/pull/23576)
- refactor(core): update production type imports from coreToolScheduler by
@abhipatel12 in
[#23498](https://github.com/google-gemini/gemini-cli/pull/23498)
- feat(cli): always prefix extension skills with colon separator by
@NTaylorMullen in
[#23566](https://github.com/google-gemini/gemini-cli/pull/23566)
- fix(core): properly support allowRedirect in policy engine by @scidomino in
[#23579](https://github.com/google-gemini/gemini-cli/pull/23579)
- fix(cli): prevent subcommand shadowing and skip auth for commands by
@mattKorwel in
[#23177](https://github.com/google-gemini/gemini-cli/pull/23177)
- fix(test): move flaky tests to non-blocking suite by @mattKorwel in
[#23259](https://github.com/google-gemini/gemini-cli/pull/23259)
- Changelog for v0.35.0-preview.3 by @gemini-cli-robot in
[#23574](https://github.com/google-gemini/gemini-cli/pull/23574)
- feat(skills): add behavioral-evals skill with fixing and promoting guides by
@abhipatel12 in
[#23349](https://github.com/google-gemini/gemini-cli/pull/23349)
- refactor(core): delete obsolete coreToolScheduler by @abhipatel12 in
[#23502](https://github.com/google-gemini/gemini-cli/pull/23502)
- Changelog for v0.35.0-preview.4 by @gemini-cli-robot in
[#23581](https://github.com/google-gemini/gemini-cli/pull/23581)
- feat(core): add LegacyAgentSession by @adamfweidman in
[#22986](https://github.com/google-gemini/gemini-cli/pull/22986)
- feat(test-utils): add TestMcpServerBuilder and support in TestRig by
@abhipatel12 in
[#23491](https://github.com/google-gemini/gemini-cli/pull/23491)
- fix(core)!: Force policy config to specify toolName by @kschaab in
[#23330](https://github.com/google-gemini/gemini-cli/pull/23330)
- eval(save_memory): add multi-turn interactive evals for memoryManager by
@SandyTao520 in
[#23572](https://github.com/google-gemini/gemini-cli/pull/23572)
- fix(telemetry): patch memory leak and enforce logPrompts privacy by
@spencer426 in
[#23281](https://github.com/google-gemini/gemini-cli/pull/23281)
- perf(cli): background IDE client to speed up initialization by @sehoon38 in
[#23603](https://github.com/google-gemini/gemini-cli/pull/23603)
- fix(cli): prevent Ctrl+D exit when input buffer is not empty by @wtanaka in
[#23306](https://github.com/google-gemini/gemini-cli/pull/23306)
- fix: ACP: separate conversational text from execute tool command title by
@sripasg in [#23179](https://github.com/google-gemini/gemini-cli/pull/23179)
- feat(evals): add behavioral evaluations for subagent routing by @Samee24 in
[#23272](https://github.com/google-gemini/gemini-cli/pull/23272)
- refactor(cli,core): foundational layout, identity management, and type safety
by @jwhelangoog in
[#23286](https://github.com/google-gemini/gemini-cli/pull/23286)
- fix(core): accurately reflect subagent tool failure in UI by @abhipatel12 in
[#23187](https://github.com/google-gemini/gemini-cli/pull/23187)
- Changelog for v0.35.0-preview.5 by @gemini-cli-robot in
[#23606](https://github.com/google-gemini/gemini-cli/pull/23606)
- feat(ui): implement refreshed UX for Composer layout by @jwhelangoog in
[#21212](https://github.com/google-gemini/gemini-cli/pull/21212)
- fix: API key input dialog user interaction when selected Gemini API Key by
@kartikangiras in
[#21057](https://github.com/google-gemini/gemini-cli/pull/21057)
- docs: update `/mcp refresh` to `/mcp reload` by @adamfweidman in
[#23631](https://github.com/google-gemini/gemini-cli/pull/23631)
- Implementation of sandbox "Write-Protected" Governance Files by @DavidAPierce
in [#23139](https://github.com/google-gemini/gemini-cli/pull/23139)
- feat(sandbox): dynamic macOS sandbox expansion and worktree support by @galz10
in [#23301](https://github.com/google-gemini/gemini-cli/pull/23301)
- fix(acp): Pass the cwd to `AcpFileSystemService` to avoid looping failures in
asking for perms to write plan md file by @sripasg in
[#23612](https://github.com/google-gemini/gemini-cli/pull/23612)
- fix(plan): sandbox path resolution in Plan Mode to prevent hallucinations by
@Adib234 in [#22737](https://github.com/google-gemini/gemini-cli/pull/22737)
- feat(ui): allow immediate user input during startup by @sehoon38 in
[#23661](https://github.com/google-gemini/gemini-cli/pull/23661)
- refactor(sandbox): reorganize Windows sandbox files by @galz10 in
[#23645](https://github.com/google-gemini/gemini-cli/pull/23645)
- fix(core): improve remote agent streaming UI and UX by @adamfweidman in
[#23633](https://github.com/google-gemini/gemini-cli/pull/23633)
- perf(cli): optimize --version startup time by @sehoon38 in
[#23671](https://github.com/google-gemini/gemini-cli/pull/23671)
- refactor(core): stop gemini CLI from producing unsafe casts by @gundermanc in
[#23611](https://github.com/google-gemini/gemini-cli/pull/23611)
- use enableAutoUpdate in test rig by @scidomino in
[#23681](https://github.com/google-gemini/gemini-cli/pull/23681)
- feat(core): change user-facing auth type from oauth2 to oauth by @adamfweidman
in [#23639](https://github.com/google-gemini/gemini-cli/pull/23639)
- chore(deps): fix npm audit vulnerabilities by @scidomino in
[#23679](https://github.com/google-gemini/gemini-cli/pull/23679)
- test(evals): fix overlapping act() deadlock in app-test-helper by @Adib234 in
[#23666](https://github.com/google-gemini/gemini-cli/pull/23666)
[#24559](https://github.com/google-gemini/gemini-cli/pull/24559)
- feat(cli): migrate nonInteractiveCli to LegacyAgentSession by @adamfweidman in
[#22987](https://github.com/google-gemini/gemini-cli/pull/22987)
- fix(core): unsafe type assertions in Core File System #19712 by
@aniketsaurav18 in
[#19739](https://github.com/google-gemini/gemini-cli/pull/19739)
- fix(ui): hide model quota in /stats and refactor quota display by @danzaharia1
in [#24206](https://github.com/google-gemini/gemini-cli/pull/24206)
- Changelog for v0.36.0 by @gemini-cli-robot in
[#24558](https://github.com/google-gemini/gemini-cli/pull/24558)
- Changelog for v0.37.0-preview.1 by @gemini-cli-robot in
[#24568](https://github.com/google-gemini/gemini-cli/pull/24568)
- docs: add missing .md extensions to internal doc links by @ishaan-arora-1 in
[#24145](https://github.com/google-gemini/gemini-cli/pull/24145)
- fix(ui): fixed table styling by @devr0306 in
[#24565](https://github.com/google-gemini/gemini-cli/pull/24565)
- fix(core): pass includeDirectories to sandbox configuration by @galz10 in
[#24573](https://github.com/google-gemini/gemini-cli/pull/24573)
- feat(ui): enable "TerminalBuffer" mode to solve flicker by @jacob314 in
[#24512](https://github.com/google-gemini/gemini-cli/pull/24512)
- docs: clarify release coordination by @scidomino in
[#24575](https://github.com/google-gemini/gemini-cli/pull/24575)
- fix(core): remove broken PowerShell translation and fix native \_\_write in
Windows sandbox by @scidomino in
[#24571](https://github.com/google-gemini/gemini-cli/pull/24571)
- Add instructions for how to start react in prod and force react to prod mode
by @jacob314 in
[#24590](https://github.com/google-gemini/gemini-cli/pull/24590)
- feat(cli): minimalist sandbox status labels by @galz10 in
[#24582](https://github.com/google-gemini/gemini-cli/pull/24582)
- Feat/browser agent metrics by @kunal-10-cloud in
[#24210](https://github.com/google-gemini/gemini-cli/pull/24210)
- test: fix Windows CI execution and resolve exposed platform failures by
@ehedlund in [#24476](https://github.com/google-gemini/gemini-cli/pull/24476)
- feat(core,cli): prioritize summary for topics (#24608) by @Abhijit-2592 in
[#24609](https://github.com/google-gemini/gemini-cli/pull/24609)
- show color by @jacob314 in
[#24613](https://github.com/google-gemini/gemini-cli/pull/24613)
- feat(cli): enable compact tool output by default (#24509) by @jwhelangoog in
[#24510](https://github.com/google-gemini/gemini-cli/pull/24510)
- fix(core): inject skill system instructions into subagent prompts if activated
by @abhipatel12 in
[#24620](https://github.com/google-gemini/gemini-cli/pull/24620)
- fix(core): improve windows sandbox reliability and fix integration tests by
@ehedlund in [#24480](https://github.com/google-gemini/gemini-cli/pull/24480)
- fix(core): ensure sandbox approvals are correctly persisted and matched for
proactive expansions by @galz10 in
[#24577](https://github.com/google-gemini/gemini-cli/pull/24577)
- feat(cli) Scrollbar for input prompt by @jacob314 in
[#21992](https://github.com/google-gemini/gemini-cli/pull/21992)
- Do not run pr-eval workflow when no steering changes detected by @alisa-alisa
in [#24621](https://github.com/google-gemini/gemini-cli/pull/24621)
- Fix restoration of topic headers. by @gundermanc in
[#24650](https://github.com/google-gemini/gemini-cli/pull/24650)
- feat(core): discourage update topic tool for simple tasks by @Samee24 in
[#24640](https://github.com/google-gemini/gemini-cli/pull/24640)
- fix(core): ensure global temp directory is always in sandbox allowed paths by
@galz10 in [#24638](https://github.com/google-gemini/gemini-cli/pull/24638)
- fix(core): detect uninitialized lines by @jacob314 in
[#24646](https://github.com/google-gemini/gemini-cli/pull/24646)
- docs: update sandboxing documentation and toolSandboxing settings by @galz10
in [#24655](https://github.com/google-gemini/gemini-cli/pull/24655)
- feat(cli): enhance tool confirmation UI and selection layout by @galz10 in
[#24376](https://github.com/google-gemini/gemini-cli/pull/24376)
- feat(acp): add support for `/about` command by @sripasg in
[#24649](https://github.com/google-gemini/gemini-cli/pull/24649)
- feat(cli): add role specific metrics to /stats by @cynthialong0-0 in
[#24659](https://github.com/google-gemini/gemini-cli/pull/24659)
- split context by @jacob314 in
[#24623](https://github.com/google-gemini/gemini-cli/pull/24623)
- fix(cli): remove -S from shebang to fix Windows and BSD execution by
@scidomino in [#24756](https://github.com/google-gemini/gemini-cli/pull/24756)
- Fix issue where topic headers can be posted back to back by @gundermanc in
[#24759](https://github.com/google-gemini/gemini-cli/pull/24759)
- fix(core): handle partial llm_request in BeforeModel hook override by
@krishdef7 in [#22326](https://github.com/google-gemini/gemini-cli/pull/22326)
- fix(ui): improve narration suppression and reduce flicker by @gundermanc in
[#24635](https://github.com/google-gemini/gemini-cli/pull/24635)
- fix(ui): fixed auth race condition causing logo to flicker by @devr0306 in
[#24652](https://github.com/google-gemini/gemini-cli/pull/24652)
- fix(browser): remove premature browser cleanup after subagent invocation by
@gsquared94 in
[#24753](https://github.com/google-gemini/gemini-cli/pull/24753)
- Revert "feat(core,cli): prioritize summary for topics (#24608)" by
@Abhijit-2592 in
[#24777](https://github.com/google-gemini/gemini-cli/pull/24777)
- relax tool sandboxing overrides for plan mode to match defaults. by
@DavidAPierce in
[#24762](https://github.com/google-gemini/gemini-cli/pull/24762)
- fix(cli): respect global environment variable allowlist by @scidomino in
[#24767](https://github.com/google-gemini/gemini-cli/pull/24767)
- fix(cli): ensure skills list outputs to stdout in non-interactive environments
by @spencer426 in
[#24566](https://github.com/google-gemini/gemini-cli/pull/24566)
- Add an eval for and fix unsafe cloning behavior. by @gundermanc in
[#24457](https://github.com/google-gemini/gemini-cli/pull/24457)
- fix(policy): allow complete_task in plan mode by @abhipatel12 in
[#24771](https://github.com/google-gemini/gemini-cli/pull/24771)
- feat(telemetry): add browser agent clearcut metrics by @gsquared94 in
[#24688](https://github.com/google-gemini/gemini-cli/pull/24688)
- feat(cli): support selective topic expansion and click-to-expand by
@Abhijit-2592 in
[#24793](https://github.com/google-gemini/gemini-cli/pull/24793)
- temporarily disable sandbox integration test on windows by @ehedlund in
[#24786](https://github.com/google-gemini/gemini-cli/pull/24786)
- Remove flakey test by @scidomino in
[#24837](https://github.com/google-gemini/gemini-cli/pull/24837)
- Alisa/approve button by @alisa-alisa in
[#24645](https://github.com/google-gemini/gemini-cli/pull/24645)
- feat(hooks): display hook system messages in UI by @mbleigh in
[#24616](https://github.com/google-gemini/gemini-cli/pull/24616)
- fix(core): propagate BeforeModel hook model override end-to-end by @krishdef7
in [#24784](https://github.com/google-gemini/gemini-cli/pull/24784)
- chore: fix formatting for behavioral eval skill reference file by @abhipatel12
in [#24846](https://github.com/google-gemini/gemini-cli/pull/24846)
- fix: use directory junctions on Windows for skill linking by @enjoykumawat in
[#24823](https://github.com/google-gemini/gemini-cli/pull/24823)
- fix(cli): prevent multiple banner increments on remount by @sehoon38 in
[#24843](https://github.com/google-gemini/gemini-cli/pull/24843)
- feat(acp): add /help command by @sripasg in
[#24839](https://github.com/google-gemini/gemini-cli/pull/24839)
- fix(core): remove tmux alternate buffer warning by @jackwotherspoon in
[#24852](https://github.com/google-gemini/gemini-cli/pull/24852)
- Improve sandbox error matching and caching by @DavidAPierce in
[#24550](https://github.com/google-gemini/gemini-cli/pull/24550)
- feat(core): add agent protocol UI types and experimental flag by @mbleigh in
[#24275](https://github.com/google-gemini/gemini-cli/pull/24275)
- feat(core): use experiment flags for default fetch timeouts by @yunaseoul in
[#24261](https://github.com/google-gemini/gemini-cli/pull/24261)
- Revert "fix(ui): improve narration suppression and reduce flicker (#2… by
@gundermanc in
[#24857](https://github.com/google-gemini/gemini-cli/pull/24857)
- refactor(cli): remove duplication in interactive shell awaiting input hint by
@JayadityaGit in
[#24801](https://github.com/google-gemini/gemini-cli/pull/24801)
- refactor(core): make LegacyAgentSession dependencies optional by @mbleigh in
[#24287](https://github.com/google-gemini/gemini-cli/pull/24287)
- Changelog for v0.37.0-preview.2 by @gemini-cli-robot in
[#24848](https://github.com/google-gemini/gemini-cli/pull/24848)
- fix(cli): always show shell command description or actual command by @jacob314
in [#24774](https://github.com/google-gemini/gemini-cli/pull/24774)
- Added flag for ept size and increased default size by @devr0306 in
[#24859](https://github.com/google-gemini/gemini-cli/pull/24859)
- fix(core): dispose Scheduler to prevent McpProgress listener leak by
@Anjaligarhwal in
[#24870](https://github.com/google-gemini/gemini-cli/pull/24870)
- fix(cli): switch default back to terminalBuffer=false and fix regressions
introduced for that mode by @jacob314 in
[#24873](https://github.com/google-gemini/gemini-cli/pull/24873)
- feat(cli): switch to ctrl+g from ctrl-x by @jacob314 in
[#24861](https://github.com/google-gemini/gemini-cli/pull/24861)
- fix: isolate concurrent browser agent instances by @gsquared94 in
[#24794](https://github.com/google-gemini/gemini-cli/pull/24794)
- docs: update MCP server OAuth redirect port documentation by @adamfweidman in
[#24844](https://github.com/google-gemini/gemini-cli/pull/24844)
**Full Changelog**:
https://github.com/google-gemini/gemini-cli/compare/v0.35.0-preview.5...v0.36.0-preview.8
https://github.com/google-gemini/gemini-cli/compare/v0.37.0-preview.2...v0.38.0-preview.0
+10 -3
View File
@@ -181,9 +181,16 @@ As described in the
rule that does not explicitly specify `modes` is considered "always active" and
will apply to Plan Mode as well.
If you want a rule to apply to other modes but _not_ to Plan Mode, you must
explicitly specify the target modes. For example, to allow `npm test` in default
and Auto-Edit modes but not in Plan Mode:
To maintain the integrity of Plan Mode as a safe research environment,
persistent tool approvals are context-aware. Approvals granted in modes like
Default or Auto-Edit do not apply to Plan Mode, ensuring that tools trusted for
implementation don't automatically execute while you're researching. However,
approvals granted while in Plan Mode are treated as intentional choices for
global trust and apply to all modes.
If you want to manually restrict a rule to other modes but _not_ to Plan Mode,
you must explicitly specify the target modes. For example, to allow `npm test`
in default and Auto-Edit modes but not in Plan Mode:
```toml
[[rule]]
+52
View File
@@ -136,6 +136,58 @@ gemini -p "build the snap"
absolute path — the path must be writable inside the container.
- Used with tools like Snapcraft or Rockcraft that require a full system.
## Tool sandboxing
Tool-level sandboxing provides granular isolation for individual tool executions
(like `shell_exec` and `write_file`) instead of sandboxing the entire Gemini CLI
process.
This approach offers better integration with your local environment for non-tool
tasks (like UI rendering and configuration loading) while still providing
security for tool-driven operations.
### How to turn off tool sandboxing
If you experience issues with tool sandboxing or prefer full-process isolation,
you can disable it by setting `security.toolSandboxing` to `false` in your
`settings.json` file.
```json
{
"security": {
"toolSandboxing": false
}
}
```
<!-- prettier-ignore -->
> [!NOTE]
> Changing the `security.toolSandboxing` setting requires a restart of Gemini
> CLI to take effect.
## Sandbox expansion
Sandbox expansion is a dynamic permission system that lets Gemini CLI request
additional permissions for a command when needed.
When a sandboxed command fails due to permission restrictions (like restricted
file paths or network access), or when a command is proactively identified as
requiring extra permissions (like `npm install`), Gemini CLI will present you
with a "Sandbox Expansion Request."
### How sandbox expansion works
1. **Detection**: Gemini CLI detects a sandbox denial or proactively identifies
a command that requires extra permissions.
2. **Request**: A modal dialog is shown, explaining which additional
permissions (e.g., specific directories or network access) are required.
3. **Approval**: If you approve the expansion, the command is executed with the
extended permissions for that specific run.
This mechanism ensures you don't have to manually re-run commands with more
permissive sandbox settings, while still maintaining control over what the AI
can access.
## Quickstart
```bash
+19 -16
View File
@@ -60,7 +60,7 @@ they appear in the UI.
| Hide Tips | `ui.hideTips` | Hide helpful tips in the UI | `false` |
| Escape Pasted @ Symbols | `ui.escapePastedAtSymbols` | When enabled, @ symbols in pasted text are escaped to prevent unintended @path expansion. | `false` |
| Show Shortcuts Hint | `ui.showShortcutsHint` | Show the "? for shortcuts" hint above the input. | `true` |
| Compact Tool Output | `ui.compactToolOutput` | Display tool outputs (like directory listings and file reads) in a compact, structured format. | `false` |
| Compact Tool Output | `ui.compactToolOutput` | Display tool outputs (like directory listings and file reads) in a compact, structured format. | `true` |
| Hide Banner | `ui.hideBanner` | Hide the application banner | `false` |
| Hide Context Summary | `ui.hideContextSummary` | Hide the context summary (GEMINI.md, MCP servers) above the input. | `false` |
| Hide CWD | `ui.footer.hideCWD` | Hide the current working directory in the footer. | `false` |
@@ -74,6 +74,8 @@ they appear in the UI.
| Show Model Info In Chat | `ui.showModelInfoInChat` | Show the model name in the chat for each model turn. | `false` |
| Show User Identity | `ui.showUserIdentity` | Show the signed-in user's identity (e.g. email) in the UI. | `true` |
| Use Alternate Screen Buffer | `ui.useAlternateBuffer` | Use an alternate screen buffer for the UI, preserving shell history. | `false` |
| Render Process | `ui.renderProcess` | Enable Ink render process for the UI. | `true` |
| Terminal Buffer | `ui.terminalBuffer` | Use the new terminal buffer architecture for rendering. | `false` |
| Use Background Color | `ui.useBackgroundColor` | Whether to use background colors in the UI. | `true` |
| Incremental Rendering | `ui.incrementalRendering` | Enable incremental rendering for the UI. This option will reduce flickering but may cause rendering artifacts. Only supported when useAlternateBuffer is enabled. | `true` |
| Show Spinner | `ui.showSpinner` | Show the spinner during operations. | `true` |
@@ -129,7 +131,7 @@ they appear in the UI.
| Sandbox Allowed Paths | `tools.sandboxAllowedPaths` | List of additional paths that the sandbox is allowed to access. | `[]` |
| Sandbox Network Access | `tools.sandboxNetworkAccess` | Whether the sandbox is allowed to access the network. | `false` |
| Enable Interactive Shell | `tools.shell.enableInteractiveShell` | Use node-pty for an interactive shell experience. Fallback to child_process still applies. | `true` |
| Show Color | `tools.shell.showColor` | Show color in shell output. | `false` |
| Show Color | `tools.shell.showColor` | Show color in shell output. | `true` |
| Use Ripgrep | `tools.useRipgrep` | Use ripgrep for file content search instead of the fallback implementation. Provides faster search performance. | `true` |
| Tool Output Truncation Threshold | `tools.truncateToolOutputThreshold` | Maximum characters to show when truncating large tool outputs. Set to 0 or negative to disable truncation. | `40000` |
| Disable LLM Correction | `tools.disableLLMCorrection` | Disable LLM-based error correction for edit tools. When enabled, tools will fail immediately if exact string matches are not found, instead of attempting to self-correct. | `true` |
@@ -138,7 +140,7 @@ they appear in the UI.
| UI Label | Setting | Description | Default |
| ------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- |
| Tool Sandboxing | `security.toolSandboxing` | Experimental tool-level sandboxing (implementation in progress). | `false` |
| Tool Sandboxing | `security.toolSandboxing` | Tool-level sandboxing. Isolates individual tools instead of the entire CLI process. | `false` |
| Disable YOLO Mode | `security.disableYoloMode` | Disable YOLO mode, even if enabled by a flag. | `false` |
| Disable Always Allow | `security.disableAlwaysAllow` | Disable "Always allow" options in tool confirmation dialogs. | `false` |
| Allow Permanent Tool Approval | `security.enablePermanentToolApproval` | Enable the "Allow for all future sessions" option in tool confirmation dialogs. | `false` |
@@ -151,22 +153,23 @@ they appear in the UI.
### Advanced
| UI Label | Setting | Description | Default |
| --------------------------------- | ------------------------------ | --------------------------------------------- | ------- |
| Auto Configure Max Old Space Size | `advanced.autoConfigureMemory` | Automatically configure Node.js memory limits | `true` |
| UI Label | Setting | Description | Default |
| --------------------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| Auto Configure Max Old Space Size | `advanced.autoConfigureMemory` | Automatically configure Node.js memory limits. Note: Because memory is allocated during the initial process boot, this setting is only read from the global user settings file and ignores workspace-level overrides. | `true` |
### Experimental
| UI Label | Setting | Description | Default |
| ------------------------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| Enable Git Worktrees | `experimental.worktrees` | Enable automated Git worktree management for parallel work. | `false` |
| 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` |
| Model Steering | `experimental.modelSteering` | Enable model steering (user hints) to guide the model during tool execution. | `false` |
| Direct Web Fetch | `experimental.directWebFetch` | Enable web fetch behavior that bypasses LLM summarization. | `false` |
| Memory Manager Agent | `experimental.memoryManager` | Replace the built-in save_memory tool with a memory manager subagent that supports adding, removing, de-duplicating, and organizing memories. | `false` |
| Enable Context Management | `experimental.contextManagement` | Enable logic for context management. | `false` |
| Topic & Update Narration | `experimental.topicUpdateNarration` | Enable the experimental Topic & Update communication model for reduced chattiness and structured progress reporting. | `false` |
| UI Label | Setting | Description | Default |
| ---------------------------------------------------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| Enable Git Worktrees | `experimental.worktrees` | Enable automated Git worktree management for parallel work. | `false` |
| 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` |
| Model Steering | `experimental.modelSteering` | Enable model steering (user hints) to guide the model during tool execution. | `false` |
| Direct Web Fetch | `experimental.directWebFetch` | Enable web fetch behavior that bypasses LLM summarization. | `false` |
| Memory Manager Agent | `experimental.memoryManager` | Replace the built-in save_memory tool with a memory manager subagent that supports adding, removing, de-duplicating, and organizing memories. | `false` |
| Use the generalist profile to manage agent contexts. | `experimental.generalistProfile` | Suitable for general coding and software development tasks. | `false` |
| Enable Context Management | `experimental.contextManagement` | Enable logic for context management. | `false` |
| Topic & Update Narration | `experimental.topicUpdateNarration` | Enable the experimental Topic & Update communication model for reduced chattiness and structured progress reporting. | `false` |
### Skills
+14 -18
View File
@@ -1,4 +1,4 @@
# Remote Subagents (experimental)
# Remote Subagents
Gemini CLI supports connecting to remote subagents using the Agent-to-Agent
(A2A) protocol. This allows Gemini CLI to interact with other agents, expanding
@@ -10,23 +10,6 @@ agents in the following repositories:
- [ADK Samples (Python)](https://github.com/google/adk-samples/tree/main/python)
- [ADK Python Contributing Samples](https://github.com/google/adk-python/tree/main/contributing/samples)
<!-- prettier-ignore -->
> [!NOTE]
> Remote subagents are currently an experimental feature.
## Configuration
To use remote subagents, you must explicitly enable them in your
`settings.json`:
```json
{
"experimental": {
"enableAgents": true
}
}
```
## Proxy support
Gemini CLI routes traffic to remote agents through an HTTP/HTTPS proxy if one is
@@ -459,3 +442,16 @@ Users can manage subagents using the following commands within the Gemini CLI:
> [!TIP]
> You can use the `@cli_help` agent within Gemini CLI for assistance
> with configuring subagents.
## Disabling remote agents
Remote subagents are enabled by default. To disable them, set `enableAgents` to
`false` in your `settings.json`:
```json
{
"experimental": {
"enableAgents": false
}
}
```
+79 -25
View File
@@ -5,15 +5,6 @@ session. They are designed to handle specific, complex tasks—like deep codebas
analysis, documentation lookup, or domain-specific reasoning—without cluttering
the main agent's context or toolset.
Subagents are enabled by default. To disable them, set `enableAgents` to `false`
in your `settings.json`:
```json
{
"experimental": { "enableAgents": false }
}
```
## What are subagents?
Subagents are "specialists" that the main Gemini agent can hire for a specific
@@ -120,10 +111,12 @@ Gemini CLI comes with the following built-in subagents:
The browser agent requires:
- **Chrome** version 144 or later (any recent stable release will work).
- **Node.js** with `npx` available (used to launch the
[`chrome-devtools-mcp`](https://www.npmjs.com/package/chrome-devtools-mcp)
server).
- **Chrome** version 144 or later (any recent stable release works).
The underlying
[`chrome-devtools-mcp`](https://www.npmjs.com/package/chrome-devtools-mcp)
server is bundled with Gemini CLI and launched automatically — no separate
installation is needed.
#### Enabling the browser agent
@@ -169,26 +162,58 @@ The available modes are:
| `isolated` | Launches Chrome with a temporary profile that is deleted after each session. Use this for clean-state automation. |
| `existing` | Attaches to an already-running Chrome instance. You must enable remote debugging first by navigating to `chrome://inspect/#remote-debugging` in Chrome. No new browser process is launched. |
#### First-run consent
The first time the browser agent is invoked, Gemini CLI displays a consent
dialog. You must accept before the browser session starts. This dialog only
appears once.
#### Configuration reference
All browser-specific settings go under `agents.browser` in your `settings.json`.
For full details, see the
[`agents.browser` configuration reference](../reference/configuration.md#agents).
| Setting | Type | Default | Description |
| :------------ | :-------- | :------------- | :---------------------------------------------------------------------------------------------- |
| `sessionMode` | `string` | `"persistent"` | How Chrome is managed: `"persistent"`, `"isolated"`, or `"existing"`. |
| `headless` | `boolean` | `false` | Run Chrome in headless mode (no visible window). |
| `profilePath` | `string` | — | Custom path to a browser profile directory. |
| `visualModel` | `string` | — | Model override for the visual agent (for example, `"gemini-2.5-computer-use-preview-10-2025"`). |
| Setting | Type | Default | Description |
| :------------------------ | :--------- | :------------- | :------------------------------------------------------------------------------ |
| `sessionMode` | `string` | `"persistent"` | How Chrome is managed: `"persistent"`, `"isolated"`, or `"existing"`. |
| `headless` | `boolean` | `false` | Run Chrome in headless mode (no visible window). |
| `profilePath` | `string` | — | Custom path to a browser profile directory. |
| `visualModel` | `string` | — | Model override for the visual agent. |
| `allowedDomains` | `string[]` | — | Restrict navigation to specific domains (for example, `["github.com"]`). |
| `disableUserInput` | `boolean` | `true` | Disable user input on the browser window during automation (non-headless only). |
| `maxActionsPerTask` | `number` | `100` | Maximum tool calls per task. The agent is terminated when the limit is reached. |
| `confirmSensitiveActions` | `boolean` | `false` | Require manual confirmation for `upload_file` and `evaluate_script`. |
| `blockFileUploads` | `boolean` | `false` | Hard-block all file upload requests from the agent. |
#### Automation overlay and input blocking
In non-headless mode, the browser agent injects a visual overlay into the
browser window to indicate that automation is in progress. By default, user
input (keyboard and mouse) is also blocked to prevent accidental interference.
You can disable this by setting `disableUserInput` to `false`.
#### Security
The browser agent enforces the following security restrictions:
The browser agent enforces several layers of security:
- **Blocked URL patterns:** `file://`, `javascript:`, `data:text/html`,
`chrome://extensions`, and `chrome://settings/passwords` are always blocked.
- **Sensitive action confirmation:** Actions like form filling, file uploads,
and form submissions require user confirmation through the standard policy
engine.
- **Domain restrictions:** When `allowedDomains` is set, the agent can only
navigate to the listed domains (and their subdomains when using `*.` prefix).
Attempting to visit a disallowed domain throws a fatal error that immediately
terminates the agent. The agent also attempts to detect and block the use of
allowed domains as proxies (e.g., via query parameters or fragments) to access
restricted content.
- **Blocked URL patterns:** The underlying MCP server blocks dangerous URL
schemes including `file://`, `javascript:`, `data:text/html`,
`chrome://extensions`, and `chrome://settings/passwords`.
- **Sensitive action confirmation:** Form filling (`fill`, `fill_form`) always
requires user confirmation through the policy engine, regardless of approval
mode. When `confirmSensitiveActions` is `true`, `upload_file` and
`evaluate_script` also require confirmation.
- **File upload blocking:** Set `blockFileUploads` to `true` to hard-block all
file upload requests, preventing the agent from uploading any files.
- **Action rate limiting:** The `maxActionsPerTask` setting (default: 100)
limits the total number of tool calls per task to prevent runaway execution.
#### Visual agent
@@ -496,6 +521,24 @@ field.
}
```
#### Safety policies (TOML)
You can restrict access to specific subagents using the CLI's **Policy Engine**.
Subagents are treated as virtual tool names for policy matching purposes.
To govern access to a subagent, create a `.toml` file in your policy directory
(e.g., `~/.gemini/policies/`):
```toml
[[rule]]
toolName = "codebase_investigator"
decision = "deny"
deny_message = "Deep codebase analysis is restricted for this session."
```
For more information on setting up fine-grained safety guardrails, see the
[Policy Engine reference](../reference/policy-engine.md#special-syntax-for-subagents).
### Optimizing your subagent
The main agent's system prompt encourages it to use an expert subagent when one
@@ -534,3 +577,14 @@ configuration, authentication, and usage instructions.
Extensions can bundle and distribute subagents. See the
[Extensions documentation](../extensions/index.md#subagents) for details on how
to package agents within an extension.
## Disabling subagents
Subagents are enabled by default. To disable them, set `enableAgents` to `false`
in your `settings.json`:
```json
{
"experimental": { "enableAgents": false }
}
```
+134 -123
View File
@@ -1,3 +1,5 @@
import { Tabs, TabItem } from '@astrojs/starlight/components';
# Gemini CLI authentication setup
To use Gemini CLI, you'll need to authenticate with Google. This guide helps you
@@ -84,19 +86,20 @@ To authenticate and use Gemini CLI with a Gemini API key:
2. Set the `GEMINI_API_KEY` environment variable to your key. For example:
**macOS/Linux**
```bash
# Replace YOUR_GEMINI_API_KEY with the key from AI Studio
export GEMINI_API_KEY="YOUR_GEMINI_API_KEY"
```
**Windows (PowerShell)**
```powershell
# Replace YOUR_GEMINI_API_KEY with the key from AI Studio
$env:GEMINI_API_KEY="YOUR_GEMINI_API_KEY"
```
<Tabs>
<TabItem label="macOS/Linux">
```bash
# Replace YOUR_GEMINI_API_KEY with the key from AI Studio
export GEMINI_API_KEY="YOUR_GEMINI_API_KEY"
```
</TabItem>
<TabItem label="Windows (PowerShell)">
```powershell
# Replace YOUR_GEMINI_API_KEY with the key from AI Studio
$env:GEMINI_API_KEY="YOUR_GEMINI_API_KEY"
```
</TabItem>
</Tabs>
To make this setting persistent, see
[Persisting Environment Variables](#persisting-vars).
@@ -131,21 +134,22 @@ or the location where you want to run your jobs.
For example:
**macOS/Linux**
```bash
# Replace with your project ID and desired location (for example, us-central1)
export GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"
export GOOGLE_CLOUD_LOCATION="YOUR_PROJECT_LOCATION"
```
**Windows (PowerShell)**
```powershell
# Replace with your project ID and desired location (for example, us-central1)
$env:GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"
$env:GOOGLE_CLOUD_LOCATION="YOUR_PROJECT_LOCATION"
```
<Tabs>
<TabItem label="macOS/Linux">
```bash
# Replace with your project ID and desired location (for example, us-central1)
export GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"
export GOOGLE_CLOUD_LOCATION="YOUR_PROJECT_LOCATION"
```
</TabItem>
<TabItem label="Windows (PowerShell)">
```powershell
# Replace with your project ID and desired location (for example, us-central1)
$env:GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"
$env:GOOGLE_CLOUD_LOCATION="YOUR_PROJECT_LOCATION"
```
</TabItem>
</Tabs>
To make any Vertex AI environment variable settings persistent, see
[Persisting Environment Variables](#persisting-vars).
@@ -157,17 +161,18 @@ Consider this authentication method if you have Google Cloud CLI installed.
If you have previously set `GOOGLE_API_KEY` or `GEMINI_API_KEY`, you must unset
them to use ADC.
**macOS/Linux**
```bash
unset GOOGLE_API_KEY GEMINI_API_KEY
```
**Windows (PowerShell)**
```powershell
Remove-Item Env:\GOOGLE_API_KEY, Env:\GEMINI_API_KEY -ErrorAction Ignore
```
<Tabs>
<TabItem label="macOS/Linux">
```bash
unset GOOGLE_API_KEY GEMINI_API_KEY
```
</TabItem>
<TabItem label="Windows (PowerShell)">
```powershell
Remove-Item Env:\GOOGLE_API_KEY, Env:\GEMINI_API_KEY -ErrorAction Ignore
```
</TabItem>
</Tabs>
1. Verify you have a Google Cloud project and Vertex AI API is enabled.
@@ -195,17 +200,18 @@ pipelines, or if your organization restricts user-based ADC or API key creation.
If you have previously set `GOOGLE_API_KEY` or `GEMINI_API_KEY`, you must unset
them:
**macOS/Linux**
```bash
unset GOOGLE_API_KEY GEMINI_API_KEY
```
**Windows (PowerShell)**
```powershell
Remove-Item Env:\GOOGLE_API_KEY, Env:\GEMINI_API_KEY -ErrorAction Ignore
```
<Tabs>
<TabItem label="macOS/Linux">
```bash
unset GOOGLE_API_KEY GEMINI_API_KEY
```
</TabItem>
<TabItem label="Windows (PowerShell)">
```powershell
Remove-Item Env:\GOOGLE_API_KEY, Env:\GEMINI_API_KEY -ErrorAction Ignore
```
</TabItem>
</Tabs>
1. [Create a service account and key](https://cloud.google.com/iam/docs/keys-create-delete)
and download the provided JSON file. Assign the "Vertex AI User" role to the
@@ -214,19 +220,20 @@ Remove-Item Env:\GOOGLE_API_KEY, Env:\GEMINI_API_KEY -ErrorAction Ignore
2. Set the `GOOGLE_APPLICATION_CREDENTIALS` environment variable to the JSON
file's absolute path. For example:
**macOS/Linux**
```bash
# Replace /path/to/your/keyfile.json with the actual path
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/your/keyfile.json"
```
**Windows (PowerShell)**
```powershell
# Replace C:\path\to\your\keyfile.json with the actual path
$env:GOOGLE_APPLICATION_CREDENTIALS="C:\path\to\your\keyfile.json"
```
<Tabs>
<TabItem label="macOS/Linux">
```bash
# Replace /path/to/your/keyfile.json with the actual path
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/your/keyfile.json"
```
</TabItem>
<TabItem label="Windows (PowerShell)">
```powershell
# Replace C:\path\to\your\keyfile.json with the actual path
$env:GOOGLE_APPLICATION_CREDENTIALS="C:\path\to\your\keyfile.json"
```
</TabItem>
</Tabs>
3. [Configure your Google Cloud Project](#set-gcp).
@@ -250,19 +257,20 @@ Remove-Item Env:\GOOGLE_API_KEY, Env:\GEMINI_API_KEY -ErrorAction Ignore
2. Set the `GOOGLE_API_KEY` environment variable:
**macOS/Linux**
```bash
# Replace YOUR_GOOGLE_API_KEY with your Vertex AI API key
export GOOGLE_API_KEY="YOUR_GOOGLE_API_KEY"
```
**Windows (PowerShell)**
```powershell
# Replace YOUR_GOOGLE_API_KEY with your Vertex AI API key
$env:GOOGLE_API_KEY="YOUR_GOOGLE_API_KEY"
```
<Tabs>
<TabItem label="macOS/Linux">
```bash
# Replace YOUR_GOOGLE_API_KEY with your Vertex AI API key
export GOOGLE_API_KEY="YOUR_GOOGLE_API_KEY"
```
</TabItem>
<TabItem label="Windows (PowerShell)">
```powershell
# Replace YOUR_GOOGLE_API_KEY with your Vertex AI API key
$env:GOOGLE_API_KEY="YOUR_GOOGLE_API_KEY"
```
</TabItem>
</Tabs>
If you see errors like `"API keys are not supported by this API..."`, your
organization might restrict API key usage for this service. Try the other
@@ -308,19 +316,20 @@ To configure Gemini CLI to use a Google Cloud project, do the following:
For example, to set the `GOOGLE_CLOUD_PROJECT_ID` variable:
**macOS/Linux**
```bash
# Replace YOUR_PROJECT_ID with your actual Google Cloud project ID
export GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"
```
**Windows (PowerShell)**
```powershell
# Replace YOUR_PROJECT_ID with your actual Google Cloud project ID
$env:GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"
```
<Tabs>
<TabItem label="macOS/Linux">
```bash
# Replace YOUR_PROJECT_ID with your actual Google Cloud project ID
export GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"
```
</TabItem>
<TabItem label="Windows (PowerShell)">
```powershell
# Replace YOUR_PROJECT_ID with your actual Google Cloud project ID
$env:GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"
```
</TabItem>
</Tabs>
To make this setting persistent, see
[Persisting Environment Variables](#persisting-vars).
@@ -333,19 +342,20 @@ persist them with the following methods:
1. **Add your environment variables to your shell configuration file:** Append
the environment variable commands to your shell's startup file.
**macOS/Linux** (for example, `~/.bashrc`, `~/.zshrc`, or `~/.profile`):
```bash
echo 'export GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"' >> ~/.bashrc
source ~/.bashrc
```
**Windows (PowerShell)** (for example, `$PROFILE`):
```powershell
Add-Content -Path $PROFILE -Value '$env:GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"'
. $PROFILE
```
<Tabs>
<TabItem label="macOS/Linux (e.g. ~/.bashrc, ~/.zshrc)">
```bash
echo 'export GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"' >> ~/.bashrc
source ~/.bashrc
```
</TabItem>
<TabItem label="Windows PowerShell (e.g. $PROFILE)">
```powershell
Add-Content -Path $PROFILE -Value '$env:GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"'
. $PROFILE
```
</TabItem>
</Tabs>
<!-- prettier-ignore -->
> [!WARNING]
@@ -361,25 +371,26 @@ persist them with the following methods:
Example for user-wide settings:
**macOS/Linux**
```bash
mkdir -p ~/.gemini
cat >> ~/.gemini/.env <<'EOF'
GOOGLE_CLOUD_PROJECT="your-project-id"
# Add other variables like GEMINI_API_KEY as needed
EOF
```
**Windows (PowerShell)**
```powershell
New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.gemini"
@"
GOOGLE_CLOUD_PROJECT="your-project-id"
# Add other variables like GEMINI_API_KEY as needed
"@ | Out-File -FilePath "$env:USERPROFILE\.gemini\.env" -Encoding utf8 -Append
```
<Tabs>
<TabItem label="macOS/Linux">
```bash
mkdir -p ~/.gemini
cat >> ~/.gemini/.env <<'EOF'
GOOGLE_CLOUD_PROJECT="your-project-id"
# Add other variables like GEMINI_API_KEY as needed
EOF
```
</TabItem>
<TabItem label="Windows (PowerShell)">
```powershell
New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.gemini"
@"
GOOGLE_CLOUD_PROJECT="your-project-id"
# Add other variables like GEMINI_API_KEY as needed
"@ | Out-File -FilePath "$env:USERPROFILE\.gemini\.env" -Encoding utf8 -Append
```
</TabItem>
</Tabs>
Variables are loaded from the first file found, not merged.
@@ -398,8 +409,8 @@ on this page.
## Running in headless mode <a id="headless"></a>
[Headless mode](../cli/headless) will use your existing authentication method,
if an existing authentication credential is cached.
[Headless mode](../cli/headless.md) will use your existing authentication
method, if an existing authentication credential is cached.
If you have not already signed in with an authentication credential, you must
configure authentication using environment variables:
+67 -46
View File
@@ -1,3 +1,5 @@
import { Tabs, TabItem } from '@astrojs/starlight/components';
# Gemini CLI installation, execution, and releases
This document provides an overview of Gemini CLI's system requirements,
@@ -32,25 +34,33 @@ Note that Gemini CLI comes pre-installed on
[**Cloud Shell**](https://docs.cloud.google.com/shell/docs) and
[**Cloud Workstations**](https://cloud.google.com/workstations).
### Install globally with npm
<Tabs>
<TabItem label="npm">
Install globally with npm:
```bash
npm install -g @google/gemini-cli
```
### Install globally with Homebrew (macOS/Linux)
</TabItem>
<TabItem label="Homebrew">
Install globally with Homebrew (macOS/Linux):
```bash
brew install gemini-cli
```
### Install globally with MacPorts (macOS)
</TabItem>
<TabItem label="MacPorts">
Install globally with MacPorts (macOS):
```bash
sudo port install gemini-cli
```
### Install with Anaconda (for restricted environments)
</TabItem>
<TabItem label="Anaconda">
Install with Anaconda (for restricted environments):
```bash
# Create and activate a new environment
@@ -61,27 +71,24 @@ conda activate gemini_env
npm install -g @google/gemini-cli
```
</TabItem>
</Tabs>
## Run Gemini CLI
For most users, we recommend running Gemini CLI with the `gemini` command:
<Tabs>
<TabItem label="Standard">
For most users, we recommend running Gemini CLI with the `gemini` command:
```bash
gemini
```
For a list of options and additional commands, see the
[CLI cheatsheet](../cli/cli-reference.md).
You can also run Gemini CLI using one of the following advanced methods:
- Run instantly with npx. You can run Gemini CLI without permanent installation.
- In a sandbox. This method offers increased security and isolation.
- From the source. This is recommended for contributors to the project.
### Run instantly with npx
[CLI cheatsheet](../cli/cli-reference.md). </TabItem> <TabItem label="npx"> Run
instantly with npx (no permanent installation required).
```bash
# Using npx (no installation required)
npx @google/gemini-cli
```
@@ -92,10 +99,10 @@ helpful for testing features still in development:
npx https://github.com/google-gemini/gemini-cli
```
### Run in a sandbox (Docker/Podman)
For security and isolation, Gemini CLI can be run inside a container. This is
the default way that the CLI executes tools that might have side effects.
</TabItem>
<TabItem label="Sandbox (Docker)">
For security and isolation, Gemini CLI can be run inside a container. This is
the default way that the CLI executes tools that might have side effects.
- **Directly from the registry:** You can run the published sandbox image
directly. This is useful for environments where you only have Docker and want
@@ -107,14 +114,15 @@ the default way that the CLI executes tools that might have side effects.
- **Using the `--sandbox` flag:** If you have Gemini CLI installed locally
(using the standard installation described above), you can instruct it to run
inside the sandbox container.
```bash
gemini --sandbox -y -p "your prompt here"
```
### Run from source (recommended for Gemini CLI contributors)
Contributors to the project will want to run the CLI directly from the source
code.
</TabItem>
<TabItem label="From source">
Contributors to the project will want to run the CLI directly from the source
code.
- **Development mode:** This method provides hot-reloading and is useful for
active development.
@@ -122,29 +130,39 @@ code.
# From the root of the repository
npm run start
```
- **Production mode (React optimizations):** This method runs the CLI with React
production mode enabled, which is useful for testing performance without
development overhead.
```bash
# From the root of the repository
npm run start:prod
```
- **Production-like mode (linked package):** This method simulates a global
installation by linking your local package. It's useful for testing a local
build in a production workflow.
```bash
# Link the local cli package to your global node_modules
npm link packages/cli
```bash
# Link the local cli package to your global node_modules
npm link packages/cli
# Now you can run your local version using the `gemini` command
gemini
```
# Now you can run your local version using the `gemini` command
gemini
```
</TabItem>
</Tabs>
## Releases
Gemini CLI has three release channels: nightly, preview, and stable. For most
users, we recommend the stable release, which is the default installation.
### Stable
New stable releases are published each week. The stable release is the promotion
of last week's `preview` release along with any bug fixes. The stable release
uses `latest` tag, but omitting the tag also installs the latest stable release
by default:
<Tabs>
<TabItem label="Stable">
New stable releases are published each week. The stable release is the promotion
of last week's `preview` release along with any bug fixes. The stable release
uses `latest` tag, but omitting the tag also installs the latest stable release
by default:
```bash
# Both commands install the latest stable release.
@@ -152,23 +170,26 @@ npm install -g @google/gemini-cli
npm install -g @google/gemini-cli@latest
```
### Preview
New preview releases will be published each week. These releases are not fully
vetted and may contain regressions or other outstanding issues. Try out the
preview release by using the `preview` tag:
</TabItem>
<TabItem label="Preview">
New preview releases will be published each week. These releases are not fully
vetted and may contain regressions or other outstanding issues. Try out the
preview release by using the `preview` tag:
```bash
npm install -g @google/gemini-cli@preview
```
### Nightly
Nightly releases are published every day. The nightly release includes all
changes from the main branch at time of release. It should be assumed there are
pending validations and issues. You can help test the latest changes by
installing with the `nightly` tag:
</TabItem>
<TabItem label="Nightly">
Nightly releases are published every day. The nightly release includes all
changes from the main branch at time of release. It should be assumed there are
pending validations and issues. You can help test the latest changes by
installing with the `nightly` tag:
```bash
npm install -g @google/gemini-cli@nightly
```
</TabItem>
</Tabs>
+6 -6
View File
@@ -22,11 +22,11 @@ With hooks, you can:
### Getting started
- **[Writing hooks guide](../hooks/writing-hooks)**: A tutorial on creating your
first hook with comprehensive examples.
- **[Best practices](../hooks/best-practices)**: Guidelines on security,
- **[Writing hooks guide](../hooks/writing-hooks.md)**: A tutorial on creating
your first hook with comprehensive examples.
- **[Best practices](../hooks/best-practices.md)**: Guidelines on security,
performance, and debugging.
- **[Hooks reference](../hooks/reference)**: The definitive technical
- **[Hooks reference](../hooks/reference.md)**: The definitive technical
specification of I/O schemas and exit codes.
## Core concepts
@@ -154,8 +154,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](../hooks/best-practices#using-hooks-securely) for
a detailed threat model.
See [Security Considerations](../hooks/best-practices.md#using-hooks-securely)
for a detailed threat model.
## Managing hooks
+82
View File
@@ -117,6 +117,88 @@ npm run test:integration:sandbox:docker
npm run test:integration:sandbox:podman
```
## Memory regression tests
Memory regression tests are designed to detect heap growth and leaks across key
CLI scenarios. They are located in the `memory-tests` directory.
These tests are distinct from standard integration tests because they measure
memory usage and compare it against committed baselines.
### Running memory tests
Memory tests are not run as part of the default `npm run test` or
`npm run test:e2e` commands. They are run nightly in CI but can be run manually:
```bash
npm run test:memory
```
### Updating baselines
If you intentionally change behavior that affects memory usage, you may need to
update the baselines. Set the `UPDATE_MEMORY_BASELINES` environment variable to
`true`:
```bash
UPDATE_MEMORY_BASELINES=true npm run test:memory
```
This will run the tests, take median snapshots, and overwrite
`memory-tests/baselines.json`. You should review the changes and commit the
updated baseline file.
### How it works
The harness (`MemoryTestHarness` in `packages/test-utils`):
- Forces garbage collection multiple times to reduce noise.
- Takes median snapshots to filter spikes.
- Compares against baselines with a 10% tolerance.
- Can analyze sustained leaks across 3 snapshots using `analyzeSnapshots()`.
## Performance regression tests
Performance regression tests are designed to detect wall-clock time, CPU usage,
and event loop delay regressions across key CLI scenarios. They are located in
the `perf-tests` directory.
These tests are distinct from standard integration tests because they measure
performance metrics and compare it against committed baselines.
### Running performance tests
Performance tests are not run as part of the default `npm run test` or
`npm run test:e2e` commands. They are run nightly in CI but can be run manually:
```bash
npm run test:perf
```
### Updating baselines
If you intentionally change behavior that affects performance, you may need to
update the baselines. Set the `UPDATE_PERF_BASELINES` environment variable to
`true`:
```bash
UPDATE_PERF_BASELINES=true npm run test:perf
```
This will run the tests multiple times (with warmup), apply IQR outlier
filtering, and overwrite `perf-tests/baselines.json`. You should review the
changes and commit the updated baseline file.
### How it works
The harness (`PerfTestHarness` in `packages/test-utils`):
- Measures wall-clock time using `performance.now()`.
- Measures CPU usage using `process.cpuUsage()`.
- Monitors event loop delay using `perf_hooks.monitorEventLoopDelay()`.
- Applies IQR (Interquartile Range) filtering to remove outlier samples.
- Compares against baselines with a 15% tolerance.
## Diagnostics
The integration test runner provides several options for diagnostics to help
+39 -11
View File
@@ -62,11 +62,13 @@ locations for these files:
**Note on environment variables in settings:** String values within your
`settings.json` and `gemini-extension.json` files can reference environment
variables using either `$VAR_NAME` or `${VAR_NAME}` syntax. These variables will
be automatically resolved when the settings are loaded. For example, if you have
an environment variable `MY_API_TOKEN`, you could use it in `settings.json` like
this: `"apiKey": "$MY_API_TOKEN"`. Additionally, each extension can have its own
`.env` file in its directory, which will be loaded automatically.
variables using `$VAR_NAME`, `${VAR_NAME}`, or `${VAR_NAME:-DEFAULT_VALUE}`
syntax. These variables will be automatically resolved when the settings are
loaded. For example, if you have an environment variable `MY_API_TOKEN`, you
could use it in `settings.json` like this: `"apiKey": "$MY_API_TOKEN"`. If you
want to provide a fallback value, use `${MY_API_TOKEN:-default-token}`.
Additionally, each extension can have its own `.env` file in its directory,
which will be loaded automatically.
**Note for Enterprise Users:** For guidance on deploying and managing Gemini CLI
in a corporate environment, please see the
@@ -265,7 +267,7 @@ their corresponding top-level category object in your `settings.json` file.
- **`ui.compactToolOutput`** (boolean):
- **Description:** Display tool outputs (like directory listings and file
reads) in a compact, structured format.
- **Default:** `false`
- **Default:** `true`
- **`ui.hideBanner`** (boolean):
- **Description:** Hide the application banner
@@ -337,6 +339,16 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `false`
- **Requires restart:** Yes
- **`ui.renderProcess`** (boolean):
- **Description:** Enable Ink render process for the UI.
- **Default:** `true`
- **Requires restart:** Yes
- **`ui.terminalBuffer`** (boolean):
- **Description:** Use the new terminal buffer architecture for rendering.
- **Default:** `false`
- **Requires restart:** Yes
- **`ui.useBackgroundColor`** (boolean):
- **Description:** Whether to use background colors in the UI.
- **Default:** `true`
@@ -1242,7 +1254,8 @@ their corresponding top-level category object in your `settings.json` file.
- **Requires restart:** Yes
- **`agents.browser.visualModel`** (string):
- **Description:** Model override for the visual agent.
- **Description:** Model for the visual agent's analyze_screenshot tool. When
set, enables the tool.
- **Default:** `undefined`
- **Requires restart:** Yes
@@ -1391,7 +1404,7 @@ their corresponding top-level category object in your `settings.json` file.
- **`tools.shell.showColor`** (boolean):
- **Description:** Show color in shell output.
- **Default:** `false`
- **Default:** `true`
- **`tools.shell.inactivityTimeout`** (number):
- **Description:** The maximum time in seconds allowed without output from the
@@ -1479,9 +1492,10 @@ their corresponding top-level category object in your `settings.json` file.
#### `security`
- **`security.toolSandboxing`** (boolean):
- **Description:** Experimental tool-level sandboxing (implementation in
progress).
- **Description:** Tool-level sandboxing. Isolates individual tools instead of
the entire CLI process.
- **Default:** `false`
- **Requires restart:** Yes
- **`security.disableYoloMode`** (boolean):
- **Description:** Disable YOLO mode, even if enabled by a flag.
@@ -1564,7 +1578,10 @@ their corresponding top-level category object in your `settings.json` file.
#### `advanced`
- **`advanced.autoConfigureMemory`** (boolean):
- **Description:** Automatically configure Node.js memory limits
- **Description:** Automatically configure Node.js memory limits. Note:
Because memory is allocated during the initial process boot, this setting is
only read from the global user settings file and ignores workspace-level
overrides.
- **Default:** `true`
- **Requires restart:** Yes
@@ -1592,6 +1609,12 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `false`
- **Requires restart:** Yes
- **`experimental.adk.agentSessionInteractiveEnabled`** (boolean):
- **Description:** Enable the agent session implementation for the interactive
CLI.
- **Default:** `false`
- **Requires restart:** Yes
- **`experimental.enableAgents`** (boolean):
- **Description:** Enable local and remote subagents.
- **Default:** `true`
@@ -1690,6 +1713,11 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `false`
- **Requires restart:** Yes
- **`experimental.generalistProfile`** (boolean):
- **Description:** Suitable for general coding and software development tasks.
- **Default:** `false`
- **Requires restart:** Yes
- **`experimental.contextManagement`** (boolean):
- **Description:** Enable logic for context management.
- **Default:** `false`
+14 -9
View File
@@ -86,13 +86,14 @@ available combinations.
#### Text Input
| Command | Action | Keys |
| -------------------------- | ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| `input.submit` | Submit the current prompt. | `Enter` |
| `input.queueMessage` | Queue the current prompt to be processed after the current task finishes. | `Tab` |
| `input.newline` | Insert a newline without submitting. | `Ctrl+Enter`<br />`Cmd/Win+Enter`<br />`Alt+Enter`<br />`Shift+Enter`<br />`Ctrl+J` |
| `input.openExternalEditor` | Open the current prompt or the plan in an external editor. | `Ctrl+X` |
| `input.paste` | Paste from the clipboard. | `Ctrl+V`<br />`Cmd/Win+V`<br />`Alt+V` |
| Command | Action | Keys |
| ------------------------------------ | ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| `input.submit` | Submit the current prompt. | `Enter` |
| `input.queueMessage` | Queue the current prompt to be processed after the current task finishes. | `Tab` |
| `input.newline` | Insert a newline without submitting. | `Ctrl+Enter`<br />`Cmd/Win+Enter`<br />`Alt+Enter`<br />`Shift+Enter`<br />`Ctrl+J` |
| `input.openExternalEditor` | Open the current prompt or the plan in an external editor. | `Ctrl+G`<br />`Ctrl+Shift+G` |
| `input.deprecatedOpenExternalEditor` | Deprecated command to open external editor. | `Ctrl+X` |
| `input.paste` | Paste from the clipboard. | `Ctrl+V`<br />`Cmd/Win+V`<br />`Alt+V` |
#### App Controls
@@ -100,9 +101,10 @@ available combinations.
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
| `app.showErrorDetails` | Toggle detailed error information. | `F12` |
| `app.showFullTodos` | Toggle the full TODO list. | `Ctrl+T` |
| `app.showIdeContextDetail` | Show IDE context details. | `Ctrl+G` |
| `app.showIdeContextDetail` | Show IDE context details. | `F4` |
| `app.toggleMarkdown` | Toggle Markdown rendering. | `Alt+M` |
| `app.toggleCopyMode` | Toggle copy mode when in alternate buffer mode. | `Ctrl+S` |
| `app.toggleCopyMode` | Toggle copy mode when in alternate buffer mode. | `F9` |
| `app.toggleMouseMode` | Toggle mouse mode (scrolling and clicking). | `Ctrl+S` |
| `app.toggleYolo` | Toggle YOLO (auto-approval) mode for tool calls. | `Ctrl+Y` |
| `app.cycleApprovalMode` | Cycle through approval modes: default (prompt), auto_edit (auto-approve edits), and plan (read-only). Plan mode is skipped when the agent is busy. | `Shift+Tab` |
| `app.showMoreLines` | Expand and collapse blocks of content when not in alternate buffer mode. | `Ctrl+O` |
@@ -126,6 +128,9 @@ available combinations.
| `background.unfocus` | Move focus from background shell to Gemini. | `Shift+Tab` |
| `background.unfocusList` | Move focus from background shell list to Gemini. | `Tab` |
| `background.unfocusWarning` | Show warning when trying to move focus away from background shell. | `Tab` |
| `app.dumpFrame` | Dump the current frame as a snapshot. | `F8` |
| `app.startRecording` | Start recording the session. | `F6` |
| `app.stopRecording` | Stop recording the session. | `F7` |
#### Extension Controls
+47 -1
View File
@@ -171,6 +171,24 @@ modes specified, it is always active.
[Customizing Plan Mode Policies](../cli/plan-mode.md#customizing-policies).
- `yolo`: A mode where all tools are auto-approved (use with extreme caution).
To maintain the integrity of Plan Mode as a safe research environment,
persistent tool approvals are context-aware. When you select **"Allow for all
future sessions"**, the policy engine explicitly includes the current mode and
all more permissive modes in the hierarchy (`plan` < `default` < `autoEdit` <
`yolo`).
- **Approvals in `plan` mode**: These represent an intentional choice to trust a
tool globally. The resulting rule explicitly includes all modes (`plan`,
`default`, `autoEdit`, and `yolo`).
- **Approvals in other modes**: These only apply to the current mode and those
more permissive. For example:
- An approval granted in **`default`** mode applies to `default`, `autoEdit`,
and `yolo`.
- An approval granted in **`autoEdit`** mode applies to `autoEdit` and `yolo`.
- An approval granted in **`yolo`** mode applies only to `yolo`. This ensures
that trust flows correctly to more permissive environments while maintaining
the safety of more restricted modes like `plan`.
## Rule matching
When a tool call is made, the engine checks it against all active rules,
@@ -304,7 +322,8 @@ priority = 10
denyMessage = "Deletion is permanent"
# (Optional) An array of approval modes where this rule is active.
modes = ["autoEdit"]
# If omitted or empty, the rule applies to all modes.
modes = ["default", "autoEdit", "yolo"]
# (Optional) A boolean to restrict the rule to interactive (true) or
# non-interactive (false) environments.
@@ -419,6 +438,33 @@ decision = "ask_user"
priority = 10
```
### Special syntax for subagents
You can secure and govern subagents using standard policy rules by treating the
subagent's name as the `toolName`.
When the main agent invokes a subagent (e.g., using the unified `invoke_agent`
tool), the Policy Engine automatically treats the target `agent_name` as a
virtual tool alias for rule matching.
**Example:**
This rule denies access to the `codebase_investigator` subagent.
```toml
[[rule]]
toolName = "codebase_investigator"
decision = "deny"
priority = 500
deny_message = "Deep codebase analysis is restricted for this session."
```
- **Backward Compatibility**: Any rules written targeting historical 1:1
subagent tool names will continue to match transparently.
- **Context differentiation**: To create rules based on **who** is calling a
tool, use the `subagent` field instead. See
[TOML rule schema](#toml-rule-schema).
## Default policies
The Gemini CLI ships with a set of default policies to provide a safe
-6
View File
@@ -22,12 +22,6 @@ nightly) or the release branch (for preview/stable).
- **Platforms:** Tests must pass on **Linux and macOS**.
<!-- prettier-ignore -->
> [!NOTE]
> Windows tests currently run with `continue-on-error: true`. While a
> failure here doesn't block the release technically, it should be
> investigated.
- **Checks:**
- **Linting:** No linting errors (ESLint, Prettier, etc.).
- **Typechecking:** No TypeScript errors.
+4
View File
@@ -1,5 +1,9 @@
# Gemini CLI releases
<!-- prettier-ignore -->
> [!IMPORTANT]
> **Coordinate with the Release Manager:** The release manager is responsible for coordinating patches and releases. Please update them before performing any of the release actions described in this document.
## `dev` vs `prod` environment
Our release flows support both `dev` and `prod` environments.
+3 -3
View File
@@ -290,7 +290,7 @@ When connecting to an OAuth-enabled server:
> OAuth authentication requires that your local machine can:
>
> - Open a web browser for authentication
> - Receive redirects on `http://localhost:7777/oauth/callback`
> - Receive redirects on `http://localhost:<random-port>/oauth/callback` (or a specific port if configured via `redirectUri`)
This feature will not work in:
@@ -323,8 +323,8 @@ Use the `/mcp auth` command to manage OAuth authentication:
if omitted)
- **`tokenUrl`** (string): OAuth token endpoint (auto-discovered if omitted)
- **`scopes`** (string[]): Required OAuth scopes
- **`redirectUri`** (string): Custom redirect URI (defaults to
`http://localhost:7777/oauth/callback`)
- **`redirectUri`** (string): Custom redirect URI (defaults to an OS-assigned
random port, e.g., `http://localhost:<random-port>/oauth/callback`)
- **`tokenParamName`** (string): Query parameter name for tokens in SSE URLs
- **`audiences`** (string[]): Audiences the token is valid for
+8
View File
@@ -94,6 +94,10 @@ const cliConfig = {
'process.env.GEMINI_SANDBOX_IMAGE_DEFAULT': JSON.stringify(
pkg.config?.sandboxImageUri,
),
'process.env.NODE_ENV': JSON.stringify(
process.env.NODE_ENV || 'production',
),
'process.env.DEV': JSON.stringify(process.env.DEV || 'false'),
},
plugins: createWasmPlugins(),
alias: {
@@ -114,6 +118,10 @@ const a2aServerConfig = {
__filename: '__chunk_filename',
__dirname: '__chunk_dirname',
'process.env.CLI_VERSION': JSON.stringify(pkg.version),
'process.env.NODE_ENV': JSON.stringify(
process.env.NODE_ENV || 'production',
),
'process.env.DEV': JSON.stringify(process.env.DEV || 'false'),
},
plugins: createWasmPlugins(),
alias: commonAliases,
+12
View File
@@ -19,6 +19,8 @@ describe('Answer vs. ask eval', () => {
* automatically modify the file, but instead asks for permission.
*/
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should not edit files when asked to inspect for bugs',
prompt: 'Inspect app.ts for bugs',
files: FILES,
@@ -42,6 +44,8 @@ describe('Answer vs. ask eval', () => {
* does modify the file.
*/
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should edit files when asked to fix bug',
prompt: 'Fix the bug in app.ts - it should add numbers not subtract',
files: FILES,
@@ -66,6 +70,8 @@ describe('Answer vs. ask eval', () => {
* automatically modify the file, but instead asks for permission.
*/
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should not edit when asking "any bugs"',
prompt: 'Any bugs in app.ts?',
files: FILES,
@@ -89,6 +95,8 @@ describe('Answer vs. ask eval', () => {
* automatically modify the file.
*/
evalTest('ALWAYS_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should not edit files when asked a general question',
prompt: 'How does app.ts work?',
files: FILES,
@@ -112,6 +120,8 @@ describe('Answer vs. ask eval', () => {
* automatically modify the file.
*/
evalTest('ALWAYS_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should not edit files when asked about style',
prompt: 'Is app.ts following good style?',
files: FILES,
@@ -135,6 +145,8 @@ describe('Answer vs. ask eval', () => {
* the agent does NOT automatically modify the file.
*/
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should not edit files when user notes an issue',
prompt: 'The add function subtracts numbers.',
files: FILES,
+49 -49
View File
@@ -10,10 +10,13 @@ import {
runEval,
prepareLogDir,
symlinkNodeModules,
withEvalRetries,
prepareWorkspace,
type BaseEvalCase,
EVAL_MODEL,
} from './test-helper.js';
import fs from 'node:fs';
import path from 'node:path';
import { DEFAULT_GEMINI_MODEL } from '@google/gemini-cli-core';
/**
* Config overrides for evals, with tool-restriction fields explicitly
@@ -29,15 +32,13 @@ interface EvalConfigOverrides {
allowedTools?: never;
/** Restricting tools via mainAgentTools in evals is forbidden. */
mainAgentTools?: never;
[key: string]: unknown;
}
export interface AppEvalCase {
name: string;
export interface AppEvalCase extends BaseEvalCase {
configOverrides?: EvalConfigOverrides;
prompt: string;
timeout?: number;
files?: Record<string, string>;
setup?: (rig: AppRig) => Promise<void>;
assert: (rig: AppRig, output: string) => Promise<void>;
}
@@ -48,56 +49,55 @@ export interface AppEvalCase {
*/
export function appEvalTest(policy: EvalPolicy, evalCase: AppEvalCase) {
const fn = async () => {
const rig = new AppRig({
configOverrides: {
model: DEFAULT_GEMINI_MODEL,
...evalCase.configOverrides,
},
});
await withEvalRetries(evalCase.name, async () => {
const rig = new AppRig({
configOverrides: {
model: EVAL_MODEL,
...evalCase.configOverrides,
},
});
const { logDir, sanitizedName } = await prepareLogDir(evalCase.name);
const logFile = path.join(logDir, `${sanitizedName}.log`);
const { logDir, sanitizedName } = await prepareLogDir(evalCase.name);
const logFile = path.join(logDir, `${sanitizedName}.log`);
try {
await rig.initialize();
try {
await rig.initialize();
const testDir = rig.getTestDir();
symlinkNodeModules(testDir);
const testDir = rig.getTestDir();
symlinkNodeModules(testDir);
// Setup initial files
if (evalCase.files) {
for (const [filePath, content] of Object.entries(evalCase.files)) {
const fullPath = path.join(testDir, filePath);
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
fs.writeFileSync(fullPath, content);
// Setup initial files
if (evalCase.files) {
// Note: AppRig does not use a separate homeDir, so we use testDir twice
await prepareWorkspace(testDir, testDir, evalCase.files);
}
// Run custom setup if provided (e.g. for breakpoints)
if (evalCase.setup) {
await evalCase.setup(rig);
}
// Render the app!
await rig.render();
// Wait for initial ready state
await rig.waitForIdle();
// Send the initial prompt
await rig.sendMessage(evalCase.prompt);
// Run assertion. Interaction-heavy tests can do their own waiting/steering here.
const output = rig.getStaticOutput();
await evalCase.assert(rig, output);
} finally {
const output = rig.getStaticOutput();
if (output) {
await fs.promises.writeFile(logFile, output);
}
await rig.unmount();
}
// Run custom setup if provided (e.g. for breakpoints)
if (evalCase.setup) {
await evalCase.setup(rig);
}
// Render the app!
await rig.render();
// Wait for initial ready state
await rig.waitForIdle();
// Send the initial prompt
await rig.sendMessage(evalCase.prompt);
// Run assertion. Interaction-heavy tests can do their own waiting/steering here.
const output = rig.getStaticOutput();
await evalCase.assert(rig, output);
} finally {
const output = rig.getStaticOutput();
if (output) {
await fs.promises.writeFile(logFile, output);
}
await rig.unmount();
}
});
};
runEval(policy, evalCase.name, fn, (evalCase.timeout ?? 60000) + 10000);
runEval(policy, evalCase, fn, (evalCase.timeout ?? 60000) + 10000);
}
+18 -6
View File
@@ -5,17 +5,21 @@
*/
import { describe, expect } from 'vitest';
import { appEvalTest, AppEvalCase } from './app-test-helper.js';
import { EvalPolicy } from './test-helper.js';
import { ApprovalMode, isRecord } from '@google/gemini-cli-core';
import { appEvalTest, type AppEvalCase } from './app-test-helper.js';
import { type EvalPolicy } from './test-helper.js';
function askUserEvalTest(policy: EvalPolicy, evalCase: AppEvalCase) {
const existingGeneral = evalCase.configOverrides?.['general'];
const generalBase = isRecord(existingGeneral) ? existingGeneral : {};
return appEvalTest(policy, {
...evalCase,
configOverrides: {
...evalCase.configOverrides,
approvalMode: ApprovalMode.DEFAULT,
general: {
...evalCase.configOverrides?.general,
approvalMode: 'default',
...generalBase,
enableAutoUpdate: false,
enableAutoUpdateNotification: false,
},
@@ -28,6 +32,8 @@ function askUserEvalTest(policy: EvalPolicy, evalCase: AppEvalCase) {
describe('ask_user', () => {
askUserEvalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'Agent uses AskUser tool to present multiple choice options',
prompt: `Use the ask_user tool to ask me what my favorite color is. Provide 3 options: red, green, or blue.`,
setup: async (rig) => {
@@ -43,6 +49,8 @@ describe('ask_user', () => {
});
askUserEvalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'Agent uses AskUser tool to clarify ambiguous requirements',
files: {
'package.json': JSON.stringify({ name: 'my-app', version: '1.0.0' }),
@@ -61,6 +69,8 @@ describe('ask_user', () => {
});
askUserEvalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'Agent uses AskUser tool before performing significant ambiguous rework',
files: {
'packages/core/src/index.ts': '// index\nexport const version = "1.0.0";',
@@ -82,8 +92,8 @@ describe('ask_user', () => {
]);
expect(confirmation, 'Expected a tool call confirmation').toBeDefined();
if (confirmation?.name === 'enter_plan_mode') {
rig.acceptConfirmation('enter_plan_mode');
if (confirmation?.toolName === 'enter_plan_mode') {
await rig.resolveTool('enter_plan_mode');
confirmation = await rig.waitForPendingConfirmation('ask_user');
}
@@ -101,6 +111,8 @@ describe('ask_user', () => {
// updates to clarify that shell command confirmation is handled by the UI.
// See fix: https://github.com/google-gemini/gemini-cli/pull/20504
askUserEvalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'Agent does NOT use AskUser to confirm shell commands',
files: {
'package.json': JSON.stringify({
+4
View File
@@ -14,6 +14,8 @@ describe('Automated tool use', () => {
* a repro by guiding the agent into using the existing deficient script.
*/
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should use automated tools (eslint --fix) to fix code style issues',
files: {
'package.json': JSON.stringify(
@@ -102,6 +104,8 @@ describe('Automated tool use', () => {
* instead of trying to edit the files itself.
*/
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should use automated tools (prettier --write) to fix formatting issues',
files: {
'package.json': JSON.stringify(
+77
View File
@@ -0,0 +1,77 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, expect } from 'vitest';
import { evalTest } from './test-helper.js';
import fs from 'node:fs';
import path from 'node:path';
describe('Background Process Monitoring', () => {
evalTest('USUALLY_PASSES', {
name: 'should naturally use read output tool to find token',
prompt:
"Run the script using 'bash generate_token.sh'. It will emit a token after a short delay and continue running. Find the token and tell me what it is.",
files: {
'generate_token.sh': `#!/bin/bash
sleep 2
echo "TOKEN=xyz123"
sleep 100
`,
},
setup: async (rig) => {
// Create .gemini directory to avoid file system error in test rig
if (rig.homeDir) {
const geminiDir = path.join(rig.homeDir, '.gemini');
fs.mkdirSync(geminiDir, { recursive: true });
}
},
assert: async (rig, result) => {
const toolCalls = rig.readToolLogs();
// Check if read_background_output was called
const hasReadCall = toolCalls.some(
(call) => call.toolRequest.name === 'read_background_output',
);
expect(
hasReadCall,
'Expected agent to call read_background_output to find the token',
).toBe(true);
// Verify that the agent found the correct token
expect(
result.includes('xyz123'),
`Expected agent to find the token xyz123. Agent output: ${result}`,
).toBe(true);
},
});
evalTest('USUALLY_PASSES', {
name: 'should naturally use list tool to verify multiple processes',
prompt:
"Start three background processes that run 'sleep 100', 'sleep 200', and 'sleep 300' respectively. Verify that all three are currently running.",
setup: async (rig) => {
// Create .gemini directory to avoid file system error in test rig
if (rig.homeDir) {
const geminiDir = path.join(rig.homeDir, '.gemini');
fs.mkdirSync(geminiDir, { recursive: true });
}
},
assert: async (rig, result) => {
const toolCalls = rig.readToolLogs();
// Check if list_background_processes was called
const hasListCall = toolCalls.some(
(call) => call.toolRequest.name === 'list_background_processes',
);
expect(
hasListCall,
'Expected agent to call list_background_processes',
).toBe(true);
},
});
});
+2
View File
@@ -3,6 +3,8 @@ import { evalTest } from './test-helper.js';
describe('CliHelpAgent Delegation', () => {
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should delegate to cli_help agent for subagent creation questions',
params: {
settings: {
+136
View File
@@ -0,0 +1,136 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {
type EvalPolicy,
runEval,
prepareLogDir,
withEvalRetries,
prepareWorkspace,
type BaseEvalCase,
} from './test-helper.js';
import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';
import { randomUUID } from 'node:crypto';
import {
Config,
type ConfigParameters,
AuthType,
ApprovalMode,
createPolicyEngineConfig,
ExtensionLoader,
IntegrityDataStatus,
makeFakeConfig,
type GeminiCLIExtension,
} from '@google/gemini-cli-core';
import { createMockSettings } from '../packages/cli/src/test-utils/settings.js';
// A minimal mock ExtensionManager to bypass integrity checks
class MockExtensionManager extends ExtensionLoader {
override getExtensions(): GeminiCLIExtension[] {
return [];
}
setRequestConsent = (): void => {};
setRequestSetting = (): void => {};
integrityManager = {
verifyExtensionIntegrity: async (): Promise<IntegrityDataStatus> =>
IntegrityDataStatus.VERIFIED,
storeExtensionIntegrity: async (): Promise<void> => undefined,
};
}
export interface ComponentEvalCase extends BaseEvalCase {
configOverrides?: Partial<ConfigParameters>;
setup?: (config: Config) => Promise<void>;
assert: (config: Config) => Promise<void>;
}
export class ComponentRig {
public config: Config | undefined;
public testDir: string;
public sessionId: string;
constructor(
private options: { configOverrides?: Partial<ConfigParameters> } = {},
) {
const uniqueId = randomUUID();
this.testDir = fs.mkdtempSync(
path.join(os.tmpdir(), `gemini-component-rig-${uniqueId.slice(0, 8)}-`),
);
this.sessionId = `test-session-${uniqueId}`;
}
async initialize() {
const settings = createMockSettings();
const policyEngineConfig = await createPolicyEngineConfig(
settings.merged,
ApprovalMode.DEFAULT,
);
const configParams: ConfigParameters = {
sessionId: this.sessionId,
targetDir: this.testDir,
cwd: this.testDir,
debugMode: false,
model: 'test-model',
interactive: false,
approvalMode: ApprovalMode.DEFAULT,
policyEngineConfig,
enableEventDrivenScheduler: false, // Don't need scheduler for direct component tests
extensionLoader: new MockExtensionManager(),
useAlternateBuffer: false,
...this.options.configOverrides,
};
this.config = makeFakeConfig(configParams);
await this.config.initialize();
// Refresh auth using USE_GEMINI to initialize the real BaseLlmClient
await this.config.refreshAuth(AuthType.USE_GEMINI);
}
async cleanup() {
fs.rmSync(this.testDir, { recursive: true, force: true });
}
}
/**
* A helper for running behavioral evaluations directly against backend components.
* It provides a fully initialized Config with real API access, bypassing the UI.
*/
export function componentEvalTest(
policy: EvalPolicy,
evalCase: ComponentEvalCase,
) {
const fn = async () => {
await withEvalRetries(evalCase.name, async () => {
const rig = new ComponentRig({
configOverrides: evalCase.configOverrides,
});
await prepareLogDir(evalCase.name);
try {
await rig.initialize();
if (evalCase.files) {
await prepareWorkspace(rig.testDir, rig.testDir, evalCase.files);
}
if (evalCase.setup) {
await evalCase.setup(rig.config!);
}
await evalCase.assert(rig.config!);
} finally {
await rig.cleanup();
}
});
};
runEval(policy, evalCase, fn, (evalCase.timeout ?? 60000) + 10000);
}
+2
View File
@@ -20,6 +20,8 @@ You are the mutation agent. Do the mutation requested.
describe('concurrency safety eval test cases', () => {
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'mutation agents are run in parallel when explicitly requested',
params: {
settings: {
+2
View File
@@ -13,6 +13,8 @@ describe('Edits location eval', () => {
* instead of creating a new one.
*/
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should update existing test file instead of creating a new one',
files: {
'package.json': JSON.stringify(
+6
View File
@@ -15,6 +15,8 @@ describe('Frugal reads eval', () => {
* nearby ranges into a single contiguous read to save tool calls.
*/
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should use ranged read when nearby lines are targeted',
files: {
'package.json': JSON.stringify({
@@ -135,6 +137,8 @@ describe('Frugal reads eval', () => {
* apart to avoid the need to read the whole file.
*/
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should use ranged read when targets are far apart',
files: {
'package.json': JSON.stringify({
@@ -204,6 +208,8 @@ describe('Frugal reads eval', () => {
* (e.g.: 10), as it's more efficient than many small ranged reads.
*/
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should read the entire file when there are many matches',
files: {
'package.json': JSON.stringify({
+2 -12
View File
@@ -13,18 +13,6 @@ import { evalTest } from './test-helper.js';
* This ensures the agent doesn't flood the context window with unnecessary search results.
*/
describe('Frugal Search', () => {
const getGrepParams = (call: any): any => {
let args = call.toolRequest.args;
if (typeof args === 'string') {
try {
args = JSON.parse(args);
} catch (e) {
// Ignore parse errors
}
}
return args;
};
/**
* Ensure that the agent makes use of either grep or ranged reads in fulfilling this task.
* The task is specifically phrased to not evoke "view" or "search" specifically because
@@ -33,6 +21,8 @@ describe('Frugal Search', () => {
* ranged reads.
*/
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should use grep or ranged read for large files',
prompt: 'What year was legacy_processor.ts written?',
files: {
+2
View File
@@ -11,6 +11,8 @@ import fs from 'node:fs/promises';
describe('generalist_agent', () => {
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should be able to use generalist agent by explicitly asking the main agent to invoke it',
params: {
settings: {
+8
View File
@@ -11,6 +11,8 @@ describe('generalist_delegation', () => {
// --- Positive Evals (Should Delegate) ---
appEvalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should delegate batch error fixing to generalist agent',
configOverrides: {
agents: {
@@ -54,6 +56,8 @@ describe('generalist_delegation', () => {
});
appEvalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should autonomously delegate complex batch task to generalist agent',
configOverrides: {
agents: {
@@ -94,6 +98,8 @@ describe('generalist_delegation', () => {
// --- Negative Evals (Should NOT Delegate - Assertive Handling) ---
appEvalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should NOT delegate simple read and fix to generalist agent',
configOverrides: {
agents: {
@@ -128,6 +134,8 @@ describe('generalist_delegation', () => {
});
appEvalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should NOT delegate simple direct question to generalist agent',
configOverrides: {
agents: {
+4
View File
@@ -26,6 +26,8 @@ describe('git repo eval', () => {
* be more consistent.
*/
evalTest('ALWAYS_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should not git add commit changes unprompted',
prompt:
'Finish this up for me by just making a targeted fix for the bug in index.ts. Do not build, install anything, or add tests',
@@ -55,6 +57,8 @@ describe('git repo eval', () => {
* instructed to not do so by default.
*/
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should git commit changes when prompted',
prompt:
'Make a targeted fix for the bug in index.ts without building, installing anything, or adding tests. Then, commit your changes.',
+12
View File
@@ -15,6 +15,8 @@ describe('grep_search_functionality', () => {
const TEST_PREFIX = 'Grep Search Functionality: ';
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should find a simple string in a file',
files: {
'test.txt': `hello
@@ -33,6 +35,8 @@ describe('grep_search_functionality', () => {
});
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should perform a case-sensitive search',
files: {
'test.txt': `Hello
@@ -63,6 +67,8 @@ describe('grep_search_functionality', () => {
});
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should return only file names when names_only is used',
files: {
'file1.txt': 'match me',
@@ -93,6 +99,8 @@ describe('grep_search_functionality', () => {
});
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should search only within the specified include_pattern glob',
files: {
'file.js': 'my_function();',
@@ -123,6 +131,8 @@ describe('grep_search_functionality', () => {
});
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should search within a specific subdirectory',
files: {
'src/main.js': 'unique_string_1',
@@ -153,6 +163,8 @@ describe('grep_search_functionality', () => {
});
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should report no matches correctly',
files: {
'file.txt': 'nothing to see here',
+7 -2
View File
@@ -5,13 +5,14 @@
*/
import { describe, expect } from 'vitest';
import { evalTest } from './test-helper.js';
import { assertModelHasOutput } from '../integration-tests/test-helper.js';
import { evalTest, assertModelHasOutput } from './test-helper.js';
describe('Hierarchical Memory', () => {
const conflictResolutionTest =
'Agent follows hierarchy for contradictory instructions';
evalTest('ALWAYS_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: conflictResolutionTest,
params: {
settings: {
@@ -48,6 +49,8 @@ What is my favorite fruit? Tell me just the name of the fruit.`,
const provenanceAwarenessTest = 'Agent is aware of memory provenance';
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: provenanceAwarenessTest,
params: {
settings: {
@@ -87,6 +90,8 @@ Provide the answer as an XML block like this:
const extensionVsGlobalTest = 'Extension memory wins over Global memory';
evalTest('ALWAYS_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: extensionVsGlobalTest,
params: {
settings: {
+4
View File
@@ -8,6 +8,8 @@ describe('interactive_commands', () => {
* intervention.
*/
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should not use interactive commands',
prompt: 'Execute tests.',
files: {
@@ -49,6 +51,8 @@ describe('interactive_commands', () => {
* Validates that the agent uses non-interactive flags when scaffolding a new project.
*/
evalTest('ALWAYS_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should use non-interactive flags when scaffolding a new app',
prompt: 'Create a new react application named my-app using vite.',
assert: async (rig, result) => {
+4 -2
View File
@@ -5,14 +5,14 @@
*/
import { describe, expect } from 'vitest';
import { act } from 'react';
import path from 'node:path';
import fs from 'node:fs';
import { appEvalTest } from './app-test-helper.js';
import { PolicyDecision } from '@google/gemini-cli-core';
describe('Model Steering Behavioral Evals', () => {
appEvalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'Corrective Hint: Model switches task based on hint during tool turn',
configOverrides: {
modelSteering: true,
@@ -52,6 +52,8 @@ describe('Model Steering Behavioral Evals', () => {
});
appEvalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'Suggestive Hint: Model incorporates user guidance mid-stream',
configOverrides: {
modelSteering: true,
+12
View File
@@ -33,6 +33,8 @@ describe('plan_mode', () => {
.filter(Boolean);
evalTest('ALWAYS_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should refuse file modification when in plan mode',
approvalMode: ApprovalMode.PLAN,
params: {
@@ -68,6 +70,8 @@ describe('plan_mode', () => {
});
evalTest('ALWAYS_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should refuse saving new documentation to the repo when in plan mode',
approvalMode: ApprovalMode.PLAN,
params: {
@@ -105,6 +109,8 @@ describe('plan_mode', () => {
});
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should enter plan mode when asked to create a plan',
approvalMode: ApprovalMode.DEFAULT,
params: {
@@ -122,6 +128,8 @@ describe('plan_mode', () => {
});
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should exit plan mode when plan is complete and implementation is requested',
approvalMode: ApprovalMode.PLAN,
params: {
@@ -169,6 +177,8 @@ describe('plan_mode', () => {
});
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should allow file modification in plans directory when in plan mode',
approvalMode: ApprovalMode.PLAN,
params: {
@@ -201,6 +211,8 @@ describe('plan_mode', () => {
});
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should create a plan in plan mode and implement it for a refactoring task',
params: {
settings,
+2
View File
@@ -11,6 +11,8 @@ import fs from 'node:fs/promises';
describe('redundant_casts', () => {
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should not add redundant or unsafe casts when modifying typescript code',
files: {
'src/cast_example.ts': `
+2
View File
@@ -3,6 +3,8 @@ import { evalTest } from './test-helper.js';
describe('Sandbox recovery', () => {
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'attempts to use additional_permissions when operation not permitted',
prompt:
'Run ./script.sh. It will fail with "Operation not permitted". When it does, you must retry running it by passing the appropriate additional_permissions.',
+28 -2
View File
@@ -5,16 +5,18 @@
*/
import { describe, expect } from 'vitest';
import { evalTest } from './test-helper.js';
import {
evalTest,
assertModelHasOutput,
checkModelOutputContent,
} from '../integration-tests/test-helper.js';
} from './test-helper.js';
describe('save_memory', () => {
const TEST_PREFIX = 'Save memory test: ';
const rememberingFavoriteColor = "Agent remembers user's favorite color";
evalTest('ALWAYS_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: rememberingFavoriteColor,
prompt: `remember that my favorite color is blue.
@@ -35,6 +37,8 @@ describe('save_memory', () => {
});
const rememberingCommandRestrictions = 'Agent remembers command restrictions';
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: rememberingCommandRestrictions,
prompt: `I don't want you to ever run npm commands.`,
@@ -54,6 +58,8 @@ describe('save_memory', () => {
const rememberingWorkflow = 'Agent remembers workflow preferences';
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: rememberingWorkflow,
prompt: `I want you to always lint after building.`,
@@ -74,6 +80,8 @@ describe('save_memory', () => {
const ignoringTemporaryInformation =
'Agent ignores temporary conversation details';
evalTest('ALWAYS_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: ignoringTemporaryInformation,
prompt: `I'm going to get a coffee.`,
@@ -97,6 +105,8 @@ describe('save_memory', () => {
const rememberingPetName = "Agent remembers user's pet's name";
evalTest('ALWAYS_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: rememberingPetName,
prompt: `Please remember that my dog's name is Buddy.`,
@@ -116,6 +126,8 @@ describe('save_memory', () => {
const rememberingCommandAlias = 'Agent remembers custom command aliases';
evalTest('ALWAYS_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: rememberingCommandAlias,
prompt: `When I say 'start server', you should run 'npm run dev'.`,
@@ -136,6 +148,8 @@ describe('save_memory', () => {
const ignoringDbSchemaLocation =
"Agent ignores workspace's database schema location";
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: ignoringDbSchemaLocation,
prompt: `The database schema for this workspace is located in \`db/schema.sql\`.`,
assert: async (rig, result) => {
@@ -155,6 +169,8 @@ describe('save_memory', () => {
const rememberingCodingStyle =
"Agent remembers user's coding style preference";
evalTest('ALWAYS_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: rememberingCodingStyle,
prompt: `I prefer to use tabs instead of spaces for indentation.`,
@@ -175,6 +191,8 @@ describe('save_memory', () => {
const ignoringBuildArtifactLocation =
'Agent ignores workspace build artifact location';
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: ignoringBuildArtifactLocation,
prompt: `In this workspace, build artifacts are stored in the \`dist/artifacts\` directory.`,
assert: async (rig, result) => {
@@ -193,6 +211,8 @@ describe('save_memory', () => {
const ignoringMainEntryPoint = "Agent ignores workspace's main entry point";
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: ignoringMainEntryPoint,
prompt: `The main entry point for this workspace is \`src/index.js\`.`,
assert: async (rig, result) => {
@@ -211,6 +231,8 @@ describe('save_memory', () => {
const rememberingBirthday = "Agent remembers user's birthday";
evalTest('ALWAYS_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: rememberingBirthday,
prompt: `My birthday is on June 15th.`,
@@ -231,6 +253,8 @@ describe('save_memory', () => {
const proactiveMemoryFromLongSession =
'Agent saves preference from earlier in conversation history';
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: proactiveMemoryFromLongSession,
params: {
settings: {
@@ -309,6 +333,8 @@ describe('save_memory', () => {
const memoryManagerRoutingPreferences =
'Agent routes global and project preferences to memory';
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: memoryManagerRoutingPreferences,
params: {
settings: {
+6
View File
@@ -21,6 +21,8 @@ describe('Shell Efficiency', () => {
};
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should use --silent/--quiet flags when installing packages',
prompt: 'Install the "lodash" package using npm.',
assert: async (rig) => {
@@ -50,6 +52,8 @@ describe('Shell Efficiency', () => {
});
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should use --no-pager with git commands',
prompt: 'Show the git log.',
assert: async (rig) => {
@@ -73,6 +77,8 @@ describe('Shell Efficiency', () => {
});
evalTest('ALWAYS_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should NOT use efficiency flags when enableShellOutputEfficiency is disabled',
params: {
settings: {
+68 -37
View File
@@ -9,10 +9,46 @@ import path from 'node:path';
import { describe, expect } from 'vitest';
import { evalTest, TEST_AGENTS } from './test-helper.js';
import { AGENT_TOOL_NAME } from '@google/gemini-cli-core';
import { evalTest, TEST_AGENTS, TestRig } from './test-helper.js';
const INDEX_TS = 'export const add = (a: number, b: number) => a + b;\n';
/**
* Helper to verify that a specific subagent was successfully invoked via the unified tool.
*/
async function expectSubagentCall(rig: TestRig, agentName: string) {
await rig.expectToolCallSuccess(
[AGENT_TOOL_NAME],
undefined,
(args: string) => {
try {
const parsed = JSON.parse(args);
return parsed.agent_name === agentName;
} catch {
return false;
}
},
);
}
/**
* Helper to check if a subagent (either via unified tool or direct name) was called.
*/
function isSubagentCalled(toolLogs: any[], agentName: string): boolean {
return toolLogs.some((l) => {
if (l.toolRequest.name === AGENT_TOOL_NAME) {
try {
const args = JSON.parse(l.toolRequest.args);
return args.agent_name === agentName;
} catch {
return false;
}
}
return l.toolRequest.name === agentName;
});
}
// A minimal package.json is used to provide a realistic workspace anchor.
// This prevents the agent from making incorrect assumptions about the environment
// and helps it properly navigate or act as if it is in a standard Node.js project.
@@ -45,6 +81,8 @@ describe('subagent eval test cases', () => {
* This tests the system prompt's subagent specific clauses.
*/
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should delegate to user provided agent with relevant expertise',
params: {
settings: {
@@ -60,7 +98,7 @@ describe('subagent eval test cases', () => {
'README.md': 'TODO: update the README.\n',
},
assert: async (rig, _result) => {
await rig.expectToolCallSuccess([TEST_AGENTS.DOCS_AGENT.name]);
await expectSubagentCall(rig, TEST_AGENTS.DOCS_AGENT.name);
},
});
@@ -69,6 +107,8 @@ describe('subagent eval test cases', () => {
* subagents are available. This helps catch orchestration overuse.
*/
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should avoid delegating trivial direct edit work',
params: {
settings: {
@@ -95,14 +135,10 @@ describe('subagent eval test cases', () => {
}>;
expect(updatedIndex).toContain('export const sum =');
expect(
toolLogs.some(
(l) => l.toolRequest.name === TEST_AGENTS.DOCS_AGENT.name,
),
).toBe(false);
expect(toolLogs.some((l) => l.toolRequest.name === 'generalist')).toBe(
expect(isSubagentCalled(toolLogs, TEST_AGENTS.DOCS_AGENT.name)).toBe(
false,
);
expect(isSubagentCalled(toolLogs, 'generalist')).toBe(false);
},
});
@@ -113,6 +149,8 @@ describe('subagent eval test cases', () => {
* This is meant to codify the "overusing Generalist" failure mode.
*/
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should prefer relevant specialist over generalist',
params: {
settings: {
@@ -134,13 +172,11 @@ describe('subagent eval test cases', () => {
},
assert: async (rig, _result) => {
const toolLogs = rig.readToolLogs() as Array<{
toolRequest: { name: string };
toolRequest: { name: string; args: string };
}>;
await rig.expectToolCallSuccess([TEST_AGENTS.TESTING_AGENT.name]);
expect(toolLogs.some((l) => l.toolRequest.name === 'generalist')).toBe(
false,
);
await expectSubagentCall(rig, TEST_AGENTS.TESTING_AGENT.name);
expect(isSubagentCalled(toolLogs, 'generalist')).toBe(false);
},
});
@@ -149,6 +185,8 @@ describe('subagent eval test cases', () => {
* naturally spans docs and tests, so multiple specialists should be used.
*/
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should use multiple relevant specialists for multi-surface task',
params: {
settings: {
@@ -173,18 +211,15 @@ describe('subagent eval test cases', () => {
},
assert: async (rig, _result) => {
const toolLogs = rig.readToolLogs() as Array<{
toolRequest: { name: string };
toolRequest: { name: string; args: string };
}>;
const readme = readProjectFile(rig, 'README.md');
await rig.expectToolCallSuccess([
TEST_AGENTS.DOCS_AGENT.name,
TEST_AGENTS.TESTING_AGENT.name,
]);
await expectSubagentCall(rig, TEST_AGENTS.DOCS_AGENT.name);
await expectSubagentCall(rig, TEST_AGENTS.TESTING_AGENT.name);
expect(readme).not.toContain('TODO: update the README.');
expect(toolLogs.some((l) => l.toolRequest.name === 'generalist')).toBe(
false,
);
expect(isSubagentCalled(toolLogs, 'generalist')).toBe(false);
},
});
@@ -193,6 +228,8 @@ describe('subagent eval test cases', () => {
* from a large pool of available subagents (10 total).
*/
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should select the correct subagent from a pool of 10 different agents',
prompt: 'Please add a new SQL table migration for a user profile.',
files: {
@@ -209,14 +246,11 @@ describe('subagent eval test cases', () => {
'package.json': MOCK_PACKAGE_JSON,
},
assert: async (rig, _result) => {
const toolLogs = rig.readToolLogs() as Array<{
toolRequest: { name: string };
}>;
await rig.expectToolCallSuccess(['database-agent']);
const toolLogs = rig.readToolLogs();
await expectSubagentCall(rig, TEST_AGENTS.DATABASE_AGENT.name);
// Ensure the generalist and other irrelevant specialists were not invoked
const uncalledAgents = [
'generalist',
TEST_AGENTS.DOCS_AGENT.name,
TEST_AGENTS.TESTING_AGENT.name,
TEST_AGENTS.CSS_AGENT.name,
@@ -229,10 +263,9 @@ describe('subagent eval test cases', () => {
];
for (const agentName of uncalledAgents) {
expect(toolLogs.some((l) => l.toolRequest.name === agentName)).toBe(
false,
);
expect(isSubagentCalled(toolLogs, agentName)).toBe(false);
}
expect(isSubagentCalled(toolLogs, 'generalist')).toBe(false);
},
});
@@ -243,6 +276,8 @@ describe('subagent eval test cases', () => {
* This test includes stress tests the subagent delegation with ~80 tools.
*/
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should select the correct subagent from a pool of 10 different agents with MCP tools present',
prompt: 'Please add a new SQL table migration for a user profile.',
setup: async (rig) => {
@@ -262,14 +297,11 @@ describe('subagent eval test cases', () => {
'package.json': MOCK_PACKAGE_JSON,
},
assert: async (rig, _result) => {
const toolLogs = rig.readToolLogs() as Array<{
toolRequest: { name: string };
}>;
await rig.expectToolCallSuccess(['database-agent']);
const toolLogs = rig.readToolLogs();
await expectSubagentCall(rig, TEST_AGENTS.DATABASE_AGENT.name);
// Ensure the generalist and other irrelevant specialists were not invoked
const uncalledAgents = [
'generalist',
TEST_AGENTS.DOCS_AGENT.name,
TEST_AGENTS.TESTING_AGENT.name,
TEST_AGENTS.CSS_AGENT.name,
@@ -282,10 +314,9 @@ describe('subagent eval test cases', () => {
];
for (const agentName of uncalledAgents) {
expect(toolLogs.some((l) => l.toolRequest.name === agentName)).toBe(
false,
);
expect(isSubagentCalled(toolLogs, agentName)).toBe(false);
}
expect(isSubagentCalled(toolLogs, 'generalist')).toBe(false);
},
});
});
+12
View File
@@ -49,6 +49,8 @@ describe('evalTest reliability logic', () => {
// Execute the test function directly
await internalEvalTest({
suiteName: 'test',
suiteType: 'behavioral',
name: 'test-api-failure',
prompt: 'do something',
assert: async () => {},
@@ -83,6 +85,8 @@ describe('evalTest reliability logic', () => {
// Expect the test function to throw immediately
await expect(
internalEvalTest({
suiteName: 'test',
suiteType: 'behavioral',
name: 'test-logic-failure',
prompt: 'do something',
assert: async () => {
@@ -108,6 +112,8 @@ describe('evalTest reliability logic', () => {
.mockResolvedValueOnce('Success');
await internalEvalTest({
suiteName: 'test',
suiteType: 'behavioral',
name: 'test-recovery',
prompt: 'do something',
assert: async () => {},
@@ -135,6 +141,8 @@ describe('evalTest reliability logic', () => {
);
await internalEvalTest({
suiteName: 'test',
suiteType: 'behavioral',
name: 'test-api-503',
prompt: 'do something',
assert: async () => {},
@@ -162,6 +170,8 @@ describe('evalTest reliability logic', () => {
try {
await expect(
internalEvalTest({
suiteName: 'test',
suiteType: 'behavioral',
name: 'test-absolute-path',
prompt: 'do something',
files: {
@@ -190,6 +200,8 @@ describe('evalTest reliability logic', () => {
try {
await expect(
internalEvalTest({
suiteName: 'test',
suiteType: 'behavioral',
name: 'test-traversal',
prompt: 'do something',
files: {
+94 -54
View File
@@ -16,10 +16,19 @@ import {
Storage,
getProjectHash,
SESSION_FILE_PREFIX,
PREVIEW_GEMINI_FLASH_MODEL,
getErrorMessage,
} from '@google/gemini-cli-core';
export * from '@google/gemini-cli-test-utils';
/**
* The default model used for all evaluations.
* Can be overridden by setting the GEMINI_MODEL environment variable.
*/
export const EVAL_MODEL =
process.env['GEMINI_MODEL'] || PREVIEW_GEMINI_FLASH_MODEL;
// Indicates the consistency expectation for this test.
// - ALWAYS_PASSES - Means that the test is expected to pass 100% of the time. These
// These tests are typically trivial and test basic functionality with unambiguous
@@ -39,19 +48,49 @@ export * from '@google/gemini-cli-test-utils';
export type EvalPolicy = 'ALWAYS_PASSES' | 'USUALLY_PASSES';
export function evalTest(policy: EvalPolicy, evalCase: EvalCase) {
runEval(
policy,
evalCase.name,
() => internalEvalTest(evalCase),
evalCase.timeout,
);
runEval(policy, evalCase, () => internalEvalTest(evalCase));
}
export async function internalEvalTest(evalCase: EvalCase) {
export async function withEvalRetries(
name: string,
attemptFn: (attempt: number) => Promise<void>,
) {
const maxRetries = 3;
let attempt = 0;
while (attempt <= maxRetries) {
try {
await attemptFn(attempt);
return; // Success! Exit the retry loop.
} catch (error: unknown) {
const errorMessage = getErrorMessage(error);
const errorCode = getApiErrorCode(errorMessage);
if (errorCode) {
const status = attempt < maxRetries ? 'RETRY' : 'SKIP';
logReliabilityEvent(name, attempt, status, errorCode, errorMessage);
if (attempt < maxRetries) {
attempt++;
console.warn(
`[Eval] Attempt ${attempt} failed with ${errorCode} Error. Retrying...`,
);
continue; // Retry
}
console.warn(
`[Eval] '${name}' failed after ${maxRetries} retries due to persistent API errors. Skipping failure to avoid blocking PR.`,
);
return; // Gracefully exit without failing the test
}
throw error; // Real failure
}
}
}
export async function internalEvalTest(evalCase: EvalCase) {
await withEvalRetries(evalCase.name, async () => {
const rig = new TestRig();
const { logDir, sanitizedName } = await prepareLogDir(evalCase.name);
const activityLogFile = path.join(logDir, `${sanitizedName}.jsonl`);
@@ -59,14 +98,21 @@ export async function internalEvalTest(evalCase: EvalCase) {
let isSuccess = false;
try {
rig.setup(evalCase.name, evalCase.params);
const setupOptions = {
...evalCase.params,
settings: {
model: { name: EVAL_MODEL },
...evalCase.params?.settings,
},
};
rig.setup(evalCase.name, setupOptions);
if (evalCase.setup) {
await evalCase.setup(rig);
}
if (evalCase.files) {
await setupTestFiles(rig, evalCase.files);
await prepareWorkspace(rig.testDir!, rig.homeDir!, evalCase.files);
}
symlinkNodeModules(rig.testDir || '');
@@ -139,37 +185,6 @@ export async function internalEvalTest(evalCase: EvalCase) {
await evalCase.assert(rig, result);
isSuccess = true;
return; // Success! Exit the retry loop.
} catch (error: unknown) {
const errorMessage =
error instanceof Error ? error.message : String(error);
const errorCode = getApiErrorCode(errorMessage);
if (errorCode) {
const status = attempt < maxRetries ? 'RETRY' : 'SKIP';
logReliabilityEvent(
evalCase.name,
attempt,
status,
errorCode,
errorMessage,
);
if (attempt < maxRetries) {
attempt++;
console.warn(
`[Eval] Attempt ${attempt} failed with ${errorCode} Error. Retrying...`,
);
continue; // Retry
}
console.warn(
`[Eval] '${evalCase.name}' failed after ${maxRetries} retries due to persistent API errors. Skipping failure to avoid blocking PR.`,
);
return; // Gracefully exit without failing the test
}
throw error; // Real failure
} finally {
if (isSuccess) {
await fs.promises.unlink(activityLogFile).catch((err) => {
@@ -188,7 +203,7 @@ export async function internalEvalTest(evalCase: EvalCase) {
);
await rig.cleanup();
}
}
});
}
function getApiErrorCode(message: string): '500' | '503' | undefined {
@@ -226,7 +241,7 @@ function logReliabilityEvent(
const reliabilityLog = {
timestamp: new Date().toISOString(),
testName,
model: process.env.GEMINI_MODEL || 'unknown',
model: process.env['GEMINI_MODEL'] || 'unknown',
attempt,
status,
errorCode,
@@ -252,9 +267,13 @@ function logReliabilityEvent(
* intentionally uses synchronous filesystem and child_process operations
* for simplicity and to ensure sequential environment preparation.
*/
async function setupTestFiles(rig: TestRig, files: Record<string, string>) {
export async function prepareWorkspace(
testDir: string,
homeDir: string,
files: Record<string, string>,
) {
const acknowledgedAgents: Record<string, Record<string, string>> = {};
const projectRoot = fs.realpathSync(rig.testDir!);
const projectRoot = fs.realpathSync(testDir);
for (const [filePath, content] of Object.entries(files)) {
if (filePath.includes('..') || path.isAbsolute(filePath)) {
@@ -290,7 +309,7 @@ async function setupTestFiles(rig: TestRig, files: Record<string, string>) {
if (Object.keys(acknowledgedAgents).length > 0) {
const ackPath = path.join(
rig.homeDir!,
homeDir,
'.gemini',
'acknowledgments',
'agents.json',
@@ -299,7 +318,7 @@ async function setupTestFiles(rig: TestRig, files: Record<string, string>) {
fs.writeFileSync(ackPath, JSON.stringify(acknowledgedAgents, null, 2));
}
const execOptions = { cwd: rig.testDir!, stdio: 'inherit' as const };
const execOptions = { cwd: testDir, stdio: 'ignore' as const };
execSync('git init --initial-branch=main', execOptions);
execSync('git config user.email "test@example.com"', execOptions);
execSync('git config user.name "Test User"', execOptions);
@@ -320,14 +339,30 @@ async function setupTestFiles(rig: TestRig, files: Record<string, string>) {
*/
export function runEval(
policy: EvalPolicy,
name: string,
evalCase: BaseEvalCase,
fn: () => Promise<void>,
timeout?: number,
timeoutOverride?: number,
) {
if (policy === 'USUALLY_PASSES' && !process.env['RUN_EVALS']) {
it.skip(name, fn);
const { name, timeout, suiteName, suiteType } = evalCase;
const targetSuiteType = process.env['EVAL_SUITE_TYPE'];
const targetSuiteName = process.env['EVAL_SUITE_NAME'];
const meta = { suiteType, suiteName };
const skipBySuiteType =
targetSuiteType && suiteType && suiteType !== targetSuiteType;
const skipBySuiteName =
targetSuiteName && suiteName && suiteName !== targetSuiteName;
const options = { timeout: timeoutOverride ?? timeout, meta };
if (
(policy === 'USUALLY_PASSES' && !process.env['RUN_EVALS']) ||
skipBySuiteType ||
skipBySuiteName
) {
it.skip(name, options, fn);
} else {
it(name, fn, timeout);
it(name, options, fn);
}
}
@@ -366,15 +401,20 @@ interface ForbiddenToolSettings {
};
}
export interface EvalCase {
export interface BaseEvalCase {
suiteName: string;
suiteType: 'behavioral' | 'component-level' | 'hero-scenario';
name: string;
timeout?: number;
files?: Record<string, string>;
}
export interface EvalCase extends BaseEvalCase {
params?: {
settings?: ForbiddenToolSettings & Record<string, unknown>;
[key: string]: unknown;
};
prompt: string;
timeout?: number;
files?: Record<string, string>;
setup?: (rig: TestRig) => Promise<void> | void;
/** Conversation history to pre-load via --resume. Each entry is a message object with type, content, etc. */
messages?: Record<string, unknown>[];
+4
View File
@@ -31,6 +31,8 @@ describe('Tool Output Masking Behavioral Evals', () => {
* It should recognize the <tool_output_masked> tag and use a tool to read the file.
*/
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should attempt to read the redirected full output file when information is masked',
params: {
security: {
@@ -167,6 +169,8 @@ Output too large. Full output available at: ${outputFilePath}
* Scenario: Information is in the preview.
*/
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should NOT read the full output file when the information is already in the preview',
params: {
security: {
+4
View File
@@ -25,6 +25,8 @@ const FILES = {
describe('tracker_mode', () => {
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should manage tasks in the tracker when explicitly requested during a bug fix',
params: {
settings: { experimental: { taskTracker: true } },
@@ -78,6 +80,8 @@ describe('tracker_mode', () => {
});
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should implicitly create tasks when asked to build a feature plan',
params: {
settings: { experimental: { taskTracker: true } },
+64
View File
@@ -0,0 +1,64 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { evalTest, TestRig } from './test-helper.js';
evalTest('USUALLY_PASSES', {
name: 'Reproduction: Agent uses Object.create() for cloning/delegation',
prompt:
'Create a utility function `createScopedConfig(config: Config, additionalDirectories: string[]): Config` in `packages/core/src/config/scoped-config.ts` that returns a new Config instance. This instance should override `getWorkspaceContext()` to include the additional directories, but delegate all other method calls (like `isPathAllowed` or `validatePathAccess`) to the original config. Note that `Config` is a complex class with private state and cannot be easily shallow-copied or reconstructed.',
files: {
'packages/core/src/config/config.ts': `
export class Config {
private _internalState = 'secret';
constructor(private workspaceContext: any) {}
getWorkspaceContext() { return this.workspaceContext; }
isPathAllowed(path: string) {
return this.getWorkspaceContext().isPathWithinWorkspace(path);
}
validatePathAccess(path: string) {
if (!this.isPathAllowed(path)) return 'Denied';
return null;
}
}`,
'packages/core/src/utils/workspaceContext.ts': `
export class WorkspaceContext {
constructor(private root: string, private additional: string[] = []) {}
getDirectories() { return [this.root, ...this.additional]; }
isPathWithinWorkspace(path: string) {
return this.getDirectories().some(d => path.startsWith(d));
}
}`,
'package.json': JSON.stringify({
name: 'test-project',
version: '1.0.0',
type: 'module',
}),
},
assert: async (rig: TestRig) => {
const filePath = 'packages/core/src/config/scoped-config.ts';
const content = rig.readFile(filePath);
if (!content) {
throw new Error(`File ${filePath} was not created.`);
}
// Strip comments to avoid false positives.
const codeWithoutComments = content.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '');
// Ensure that the agent did not use Object.create() in the implementation.
// We check for the call pattern specifically using a regex to avoid false positives in comments.
const hasObjectCreate = /\bObject\.create\s*\(/.test(codeWithoutComments);
if (hasObjectCreate) {
throw new Error(
'Evaluation Failed: Agent used Object.create() for cloning. ' +
'This behavior is forbidden by the project lint rules (no-restricted-syntax). ' +
'Implementation found:\n\n' +
content,
);
}
},
});
+145
View File
@@ -5,6 +5,8 @@
*/
import { describe, expect } from 'vitest';
import fs from 'node:fs';
import path from 'node:path';
import { evalTest } from './test-helper.js';
describe('update_topic_behavior', () => {
@@ -113,4 +115,147 @@ describe('update_topic_behavior', () => {
}
},
});
evalTest('USUALLY_PASSES', {
name: 'update_topic should NOT be used for informational coding tasks (Obvious)',
approvalMode: 'default',
prompt:
'Explain the difference between Map and Object in JavaScript and provide a performance-focused code snippet for each.',
files: {
'.gemini/settings.json': JSON.stringify({
experimental: {
topicUpdateNarration: true,
},
}),
},
assert: async (rig) => {
const toolLogs = rig.readToolLogs();
const topicCalls = toolLogs.filter(
(l) => l.toolRequest.name === UPDATE_TOPIC_TOOL_NAME,
);
expect(
topicCalls.length,
`Expected 0 update_topic calls for an informational task, but found ${topicCalls.length}`,
).toBe(0);
},
});
evalTest('USUALLY_PASSES', {
name: 'update_topic should NOT be used for surgical symbol searches (Grey Area)',
approvalMode: 'default',
prompt:
"Find the file where the 'UPDATE_TOPIC_TOOL_NAME' constant is defined.",
files: {
'packages/core/src/tools/tool-names.ts':
"export const UPDATE_TOPIC_TOOL_NAME = 'update_topic';",
'.gemini/settings.json': JSON.stringify({
experimental: {
topicUpdateNarration: true,
},
}),
},
assert: async (rig) => {
const toolLogs = rig.readToolLogs();
const topicCalls = toolLogs.filter(
(l) => l.toolRequest.name === UPDATE_TOPIC_TOOL_NAME,
);
expect(
topicCalls.length,
`Expected 0 update_topic calls for a surgical symbol search, but found ${topicCalls.length}`,
).toBe(0);
},
});
evalTest('USUALLY_PASSES', {
name: 'update_topic should be used for medium complexity multi-step tasks',
prompt:
'Refactor the `users-api` project. Move the routing logic from src/app.ts into a new file src/routes.ts, and update app.ts to use the new routes file.',
files: {
'package.json': JSON.stringify(
{
name: 'users-api',
version: '1.0.0',
},
null,
2,
),
'src/app.ts': `
import express from 'express';
const app = express();
app.get('/users', (req, res) => {
res.json([{id: 1, name: 'Alice'}]);
});
app.post('/users', (req, res) => {
res.status(201).send();
});
export default app;
`,
'.gemini/settings.json': JSON.stringify({
experimental: {
topicUpdateNarration: true,
},
}),
},
assert: async (rig) => {
const toolLogs = rig.readToolLogs();
const topicCalls = toolLogs.filter(
(l) => l.toolRequest.name === UPDATE_TOPIC_TOOL_NAME,
);
// This is a multi-step task (read, create new file, edit old file).
// It should clear the bar and use update_topic at least at the start and end.
expect(topicCalls.length).toBeGreaterThanOrEqual(2);
// Verify it actually did the refactoring to ensure it didn't just fail immediately
expect(fs.existsSync(path.join(rig.testDir, 'src/routes.ts'))).toBe(true);
},
});
/**
* Regression test for a bug where update_topic was called multiple times in a
* row. We have seen cases of this occurring in earlier versions of the update_topic
* system instruction, prior to https://github.com/google-gemini/gemini-cli/pull/24640.
* This test demonstrated that there are cases where it can still occur and validates
* the prompt change that improves the behavior.
*/
evalTest('USUALLY_PASSES', {
name: 'update_topic should not be called twice in a row',
prompt: `
We need to build a C compiler.
Before you write any code, you must formally declare your strategy.
First, declare that you will build a Lexer.
Then, immediately realize that is wrong and declare that you will actually build a Parser instead.
Finally, create 'parser.c'.
`,
files: {
'package.json': JSON.stringify({ name: 'test-project' }),
'.gemini/settings.json': JSON.stringify({
experimental: {
topicUpdateNarration: true,
},
}),
},
assert: async (rig) => {
const toolLogs = rig.readToolLogs();
// Check for back-to-back update_topic calls
for (let i = 1; i < toolLogs.length; i++) {
if (
toolLogs[i - 1].toolRequest.name === UPDATE_TOPIC_TOOL_NAME &&
toolLogs[i].toolRequest.name === UPDATE_TOPIC_TOOL_NAME
) {
throw new Error(
`Detected back-to-back ${UPDATE_TOPIC_TOOL_NAME} calls at index ${i - 1} and ${i}`,
);
}
}
},
});
});
+2
View File
@@ -9,6 +9,8 @@ import { evalTest } from './test-helper.js';
describe('validation_fidelity', () => {
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should perform exhaustive validation autonomously when guided by system instructions',
files: {
'src/types.ts': `
@@ -9,6 +9,8 @@ import { evalTest } from './test-helper.js';
describe('validation_fidelity_pre_existing_errors', () => {
evalTest('USUALLY_PASSES', {
suiteName: 'default',
suiteType: 'behavioral',
name: 'should handle pre-existing project errors gracefully during validation',
files: {
'src/math.ts': `
+4 -1
View File
@@ -24,7 +24,10 @@ export default defineConfig({
environment: 'node',
globals: true,
alias: {
react: path.resolve(__dirname, '../node_modules/react'),
'@google/gemini-cli-core': path.resolve(
__dirname,
'../packages/core/index.ts',
),
},
setupFiles: [path.resolve(__dirname, '../packages/cli/test-setup.ts')],
server: {
@@ -0,0 +1,6 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I'll check the dynamic content page on the localhost server."},{"functionCall":{"name":"invoke_agent","args":{"agent_name":"browser_agent","prompt":"Navigate to http://127.0.0.1:18923/dynamic.html, wait for the dynamic content to load, then capture the accessibility tree and report what content appeared"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":40,"totalTokenCount":140}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"navigate_page","args":{"url":"http://127.0.0.1:18923/dynamic.html"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":20,"totalTokenCount":120}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"wait_for","args":{"selector":"#dynamic-content","state":"visible","timeout":5000}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":150,"candidatesTokenCount":25,"totalTokenCount":175}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_snapshot","args":{}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":180,"candidatesTokenCount":15,"totalTokenCount":195}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"complete_task","args":{"result":{"success":true,"summary":"Dynamic content loaded successfully. Found heading 'Content Loaded', message 'This content was loaded dynamically via JavaScript.', and a list with items: Item Alpha, Item Beta, Item Gamma."}}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":40,"totalTokenCount":240}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The dynamic content page loaded successfully:\n\n- **Heading**: Content Loaded\n- **Message**: This content was loaded dynamically via JavaScript\n- **Items**: Item Alpha, Item Beta, Item Gamma\n\nThe JavaScript-rendered content appeared after the initial page load, replacing the 'Loading...' indicator."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":60,"totalTokenCount":260}}]}
@@ -0,0 +1,9 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I'll fill out the contact form on the localhost server."},{"functionCall":{"name":"invoke_agent","args":{"agent_name":"browser_agent","prompt":"Navigate to http://127.0.0.1:18923/form.html, fill in the name field with 'Test User', the email field with 'test@example.com', the message field with 'Hello World', and submit the form"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":50,"totalTokenCount":150}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"navigate_page","args":{"url":"http://127.0.0.1:18923/form.html"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":20,"totalTokenCount":120}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"fill","args":{"selector":"#name","value":"Test User"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":150,"candidatesTokenCount":25,"totalTokenCount":175}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"fill","args":{"selector":"#email","value":"test@example.com"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":180,"candidatesTokenCount":25,"totalTokenCount":205}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"fill","args":{"selector":"#message","value":"Hello World"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":210,"candidatesTokenCount":25,"totalTokenCount":235}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"click","args":{"selector":"#submit-btn"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":240,"candidatesTokenCount":20,"totalTokenCount":260}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_snapshot","args":{}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":270,"candidatesTokenCount":15,"totalTokenCount":285}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"complete_task","args":{"result":{"success":true,"summary":"Successfully filled and submitted the contact form. Navigated to form-result.html which shows 'Form Submitted Successfully'."}}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":300,"candidatesTokenCount":40,"totalTokenCount":340}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I've successfully filled out and submitted the contact form:\n\n- **Name**: Test User\n- **Email**: test@example.com\n- **Message**: Hello World\n\nAfter submission, the page navigated to the result page showing 'Form Submitted Successfully'."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":300,"candidatesTokenCount":60,"totalTokenCount":360}}]}
@@ -0,0 +1,9 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I'll go through the multi-step flow on the localhost server."},{"functionCall":{"name":"invoke_agent","args":{"agent_name":"browser_agent","prompt":"Navigate to http://127.0.0.1:18923/multi-step/step1.html, fill in 'testuser' as the username, click Next, then on step 2 select 'Option B' and click Finish. Report the final result page content."}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":50,"totalTokenCount":150}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"navigate_page","args":{"url":"http://127.0.0.1:18923/multi-step/step1.html"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":20,"totalTokenCount":120}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"fill","args":{"selector":"#username","value":"testuser"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":150,"candidatesTokenCount":25,"totalTokenCount":175}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"click","args":{"selector":"#next-btn"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":180,"candidatesTokenCount":20,"totalTokenCount":200}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_snapshot","args":{}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":210,"candidatesTokenCount":15,"totalTokenCount":225}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"click","args":{"selector":"#finish-btn"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":240,"candidatesTokenCount":20,"totalTokenCount":260}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_snapshot","args":{}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":270,"candidatesTokenCount":15,"totalTokenCount":285}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"complete_task","args":{"result":{"success":true,"summary":"Completed all steps. Step 1: entered username 'testuser'. Step 2: selected default option. Final result page shows 'Multi-Step Complete' with '✓ Complete' status badge."}}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":300,"candidatesTokenCount":40,"totalTokenCount":340}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I've completed the multi-step flow:\n\n1. **Step 1**: Entered 'testuser' as username and clicked Next\n2. **Step 2**: Confirmed selection and clicked Finish\n3. **Result**: Final page shows 'Multi-Step Complete' with a '✓ Complete' status badge\n\nAll steps were successfully navigated."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":300,"candidatesTokenCount":60,"totalTokenCount":360}}]}
@@ -0,0 +1,5 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I'll navigate to the localhost page and read its content using the browser agent."},{"functionCall":{"name":"invoke_agent","args":{"agent_name":"browser_agent","prompt":"Navigate to http://127.0.0.1:18923/index.html and tell me the page title and list all links on the page"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":40,"totalTokenCount":140}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"navigate_page","args":{"url":"http://127.0.0.1:18923/index.html"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":20,"totalTokenCount":120}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_snapshot","args":{}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":150,"candidatesTokenCount":20,"totalTokenCount":170}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"complete_task","args":{"result":{"success":true,"summary":"Page title is 'Test Fixture - Home'. Found 3 links: Contact Form (/form.html), Multi-Step Flow (/multi-step/step1.html), Dynamic Content (/dynamic.html)."}}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":40,"totalTokenCount":240}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The localhost test fixture page has:\n\n**Title**: Test Fixture - Home\n\n**Links**:\n1. Contact Form (form.html)\n2. Multi-Step Flow (multi-step/step1.html)\n3. Dynamic Content (dynamic.html)\n\nThe page also has a heading 'Test Fixture Home Page' and footer content."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":60,"totalTokenCount":260}}]}
@@ -0,0 +1,5 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I'll take a screenshot of the localhost test page."},{"functionCall":{"name":"invoke_agent","args":{"agent_name":"browser_agent","prompt":"Navigate to http://127.0.0.1:18923/index.html and take a screenshot of the page"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":35,"totalTokenCount":135}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"navigate_page","args":{"url":"http://127.0.0.1:18923/index.html"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":20,"totalTokenCount":120}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_screenshot","args":{}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":150,"candidatesTokenCount":15,"totalTokenCount":165}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"complete_task","args":{"result":{"success":true,"summary":"Screenshot captured of the localhost test fixture home page showing the heading, navigation links, and footer.","data":{"screenshotTaken":true}}}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":40,"totalTokenCount":240}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I've captured a screenshot of the localhost test fixture page. The screenshot shows the 'Test Fixture Home Page' heading with navigation links to the Contact Form, Multi-Step Flow, and Dynamic Content pages, along with the footer section."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":50,"totalTokenCount":250}}]}
@@ -0,0 +1,171 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
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';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
describe('browser-agent-localhost', () => {
let rig: TestRig;
const browserSettings = {
agents: {
overrides: {
browser_agent: {
enabled: true,
},
},
browser: {
headless: true,
sessionMode: 'isolated' as const,
},
},
};
beforeEach(() => {
rig = new TestRig();
});
afterEach(async () => {
await rig.cleanup();
});
it('should navigate to localhost fixture and read page content', async () => {
rig.setup('localhost-navigate', {
fakeResponsesPath: join(
__dirname,
'browser-agent-localhost.navigate.responses',
),
settings: browserSettings,
});
const result = await rig.run({
args: 'Navigate to http://127.0.0.1:18923/index.html and tell me the page title and list all links.',
});
assertModelHasOutput(result);
const toolLogs = rig.readToolLogs();
const browserAgentCall = toolLogs.find(
(t) =>
t.toolRequest.name === 'invoke_agent' &&
JSON.parse(t.toolRequest.args).agent_name === 'browser_agent',
);
expect(
browserAgentCall,
'Expected browser_agent to be called',
).toBeDefined();
});
it('should fill out and submit a form on localhost', async () => {
rig.setup('localhost-form', {
fakeResponsesPath: join(
__dirname,
'browser-agent-localhost.form.responses',
),
settings: browserSettings,
});
const result = await rig.run({
args: "Navigate to http://127.0.0.1:18923/form.html, fill in name='Test User', email='test@example.com', message='Hello World', and submit the form.",
});
assertModelHasOutput(result);
const toolLogs = rig.readToolLogs();
const browserAgentCall = toolLogs.find(
(t) =>
t.toolRequest.name === 'invoke_agent' &&
JSON.parse(t.toolRequest.args).agent_name === 'browser_agent',
);
expect(
browserAgentCall,
'Expected browser_agent to be called',
).toBeDefined();
});
it('should navigate through a multi-step flow', async () => {
rig.setup('localhost-multistep', {
fakeResponsesPath: join(
__dirname,
'browser-agent-localhost.multistep.responses',
),
settings: browserSettings,
});
const result = await rig.run({
args: "Go to http://127.0.0.1:18923/multi-step/step1.html, fill in 'testuser' as username, click Next, then click Finish on step 2. Report the result.",
});
assertModelHasOutput(result);
const toolLogs = rig.readToolLogs();
const browserAgentCall = toolLogs.find(
(t) =>
t.toolRequest.name === 'invoke_agent' &&
JSON.parse(t.toolRequest.args).agent_name === 'browser_agent',
);
expect(
browserAgentCall,
'Expected browser_agent to be called',
).toBeDefined();
});
it('should handle dynamically loaded content', async () => {
rig.setup('localhost-dynamic', {
fakeResponsesPath: join(
__dirname,
'browser-agent-localhost.dynamic.responses',
),
settings: browserSettings,
});
const result = await rig.run({
args: 'Navigate to http://127.0.0.1:18923/dynamic.html, wait for content to load, and tell me what items appear.',
});
assertModelHasOutput(result);
const toolLogs = rig.readToolLogs();
const browserAgentCall = toolLogs.find(
(t) =>
t.toolRequest.name === 'invoke_agent' &&
JSON.parse(t.toolRequest.args).agent_name === 'browser_agent',
);
expect(
browserAgentCall,
'Expected browser_agent to be called',
).toBeDefined();
});
it('should take a screenshot of localhost page', async () => {
rig.setup('localhost-screenshot', {
fakeResponsesPath: join(
__dirname,
'browser-agent-localhost.screenshot.responses',
),
settings: browserSettings,
});
const result = await rig.run({
args: 'Navigate to http://127.0.0.1:18923/index.html and take a screenshot.',
});
assertModelHasOutput(result);
const toolLogs = rig.readToolLogs();
const browserCalls = toolLogs.filter(
(t) =>
t.toolRequest.name === 'invoke_agent' &&
JSON.parse(t.toolRequest.args).agent_name === 'browser_agent',
);
expect(browserCalls.length).toBeGreaterThan(0);
});
});
@@ -1,4 +1,5 @@
{"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":"I have opened the page and the title is 'Example Domain'."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":30,"totalTokenCount":230}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The task is complete. The page title is 'Example Domain'."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":300,"candidatesTokenCount":20,"totalTokenCount":320}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Done."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":400,"candidatesTokenCount":5,"totalTokenCount":405}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I'll open https://example.com and check the page title for you."},{"functionCall":{"name":"invoke_agent","args":{"agent_name":"browser_agent","prompt":"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":[{"functionCall":{"name":"navigate_page","args":{"url":"https://example.com"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":20,"totalTokenCount":120}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_snapshot","args":{}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":150,"candidatesTokenCount":20,"totalTokenCount":170}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"complete_task","args":{"result":{"success":true,"summary":"The page title is 'Example Domain'."}}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":30,"totalTokenCount":230}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I have opened the page and the title is 'Example Domain'. The browser session has been cleaned up successfully."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":30,"totalTokenCount":230}}]}
@@ -0,0 +1,8 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I'll launch two browser agents concurrently to check both repositories."},{"functionCall":{"name":"invoke_agent","args":{"agent_name":"browser_agent","prompt":"Navigate to https://example.com and get the page title"}}},{"functionCall":{"name":"invoke_agent","args":{"agent_name":"browser_agent","prompt":"Navigate to https://example.com and get the page title"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":50,"totalTokenCount":150}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"navigate_page","args":{"url":"https://example.com"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":20,"totalTokenCount":120}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"navigate_page","args":{"url":"https://example.com"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":20,"totalTokenCount":120}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_snapshot","args":{}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":150,"candidatesTokenCount":15,"totalTokenCount":165}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_snapshot","args":{}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":150,"candidatesTokenCount":15,"totalTokenCount":165}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"complete_task","args":{"result":{"success":true,"summary":"Page title is Example Domain."}}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":30,"totalTokenCount":230}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"complete_task","args":{"result":{"success":true,"summary":"Page title is Example Domain."}}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":30,"totalTokenCount":230}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Both browser agents completed successfully. Agent 1 and Agent 2 both navigated to their respective pages and confirmed the page titles."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":300,"candidatesTokenCount":40,"totalTokenCount":340}}]}
@@ -1,2 +1,5 @@
{"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":"I'll navigate to https://example.com and analyze the links on the page."},{"functionCall":{"name":"invoke_agent","args":{"agent_name":"browser_agent","prompt":"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":[{"functionCall":{"name":"navigate_page","args":{"url":"https://example.com"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":20,"totalTokenCount":120}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_snapshot","args":{}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":150,"candidatesTokenCount":20,"totalTokenCount":170}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"complete_task","args":{"result":{"success":true,"summary":"Found one link on https://example.com: 'More information...' linking to the IANA website for details about reserved domains."}}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":40,"totalTokenCount":240}}]}
{"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}}]}
@@ -1,2 +1,5 @@
{"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":"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":"invoke_agent","args":{"agent_name":"browser_agent","prompt":"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":[{"functionCall":{"name":"navigate_page","args":{"url":"https://example.com"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":30,"totalTokenCount":130}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_snapshot","args":{}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":150,"candidatesTokenCount":20,"totalTokenCount":170}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"complete_task","args":{"result":{"success":true,"summary":"Successfully navigated to https://example.com. The page title is 'Example Domain' and the main content states: 'This domain is for use in illustrative examples in documents.'"}}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":40,"totalTokenCount":240}}]}
{"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,26 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I will invoke the browser agent to get the page title of example.com."}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":10491,"candidatesTokenCount":16,"totalTokenCount":10587,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10491}],"thoughtsTokenCount":80}},{"candidates":[{"content":{"parts":[{"functionCall":{"name":"invoke_agent","args":{"prompt":"Navigate to example.com and return the page title.","agent_name":"browser_agent"},"id":"1zgnzmz8"},"thoughtSignature":"EpgDCpUDAb4+9vtATVUpO7R/Du3cyW+qLtXqHV5MxjoY/uOyN6tesv96PEfcvXarQ/u24REH9DCw2AIG00h9WM2jRtfgbU544f4lEQn0dLEkAJCQaiBcxx8yPND+qKmwq8PFHo6ESQs3nssi3XOqfiA7YMxxY2vEx4GDZfieEeS/nyTe3F7A7dEoMnE2VnMWRVdAAW1F2K2ZeDQZSDrJxtelkn0dGd/MS0R6iNDENWg9QWJQlok4xttspRJFLS5BvkvnkBcUWQFQlo60AQb1Vbo8MQvV8WHUPORRePj4iW3IyxOohmb7uMfDo7UiRyv3Li8AAga7+oSUU9HSf5XZrjSL0juzJpYxxCqnIuj1/ZIY5SBSwGAWKuLQwmbo433bKij9HnY8n/MeNiDgwMiBX56mNlAOIqVLR9Qskod14H4hB+xAbvFy8j8hwf3hksLGjhM9BTBlcBQ2dbor5OUmZ/C7XsnLZvnhw+in7ji2tHz/68/4gmIN08khU1BRvfZdxtX8eQoVLcCfvwkGX7/drD9bM0TrjZuWXh9o"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":10491,"candidatesTokenCount":52,"totalTokenCount":10623,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10491}],"thoughtsTokenCount":80}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":10491,"candidatesTokenCount":52,"totalTokenCount":10623,"cachedContentTokenCount":8126,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10491}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":8126}],"thoughtsTokenCount":80}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"list_pages","args":{"wait_for_previous":true},"id":"q9a8dwir"},"thoughtSignature":"Et4DCtsDAb4+9vtRa6LmDS0G8Z6sSxYi8TMEuRycQ7Zi/yYxo5sLhVJnIqMZdJiq+q2ZgvFiS9xxX6rrUTj+a5eEN2UrHC5MmzKVu9TSMs/Vd1XV9ayzef54tmsLy7PDb5Ja1ZPo8iRHIvnleWw/JgcwckXRPO/NwAyFfmYQ9lM93nRbZxnQQ12jBjS9R0f+TkGZyy26HDLl09w0psqNW8fwCm+nWc+Ouf8V/Gu6QOTh+VBZo+JP0HbMm25IHc7BlKoMtKNj9C0BVTVXyEzKelCiciR3VcfqdqdMmaVK8UXWGtcEnwyaObbtOPlav0sTFhHZsmV6P4HKUFoiYM/An2p66sGA4YwZYnhTsSXmbxb2pVNQWTjVJaRyJOsBnAiA5sxqqR/exo84YDsvBzGJ/1Y99Q/vHRNWVKgwfV8k5mkV7zLxJmh5oILaEHGYCVriVd/v419qWUZv3QAu0vcWwoqc65SZtfkG1JKMfrPuWXk8i1s2dSYwGg7tCtL8laocy0+l14ga8sxAsmvWAnVUQCANTxyvQDFuEMReyr2cjQjQXohbm2Q02wGXvkYXT9UW15V+KTdt8DmR28x0mZBseIJOAjmx9ZyaGibOMc72UbS+tf93RVGRO3Fey60+uhQvGA=="}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":8627,"candidatesTokenCount":18,"totalTokenCount":8742,"promptTokensDetails":[{"modality":"TEXT","tokenCount":8627}],"thoughtsTokenCount":97}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":8627,"candidatesTokenCount":18,"totalTokenCount":8742,"cachedContentTokenCount":8066,"promptTokensDetails":[{"modality":"TEXT","tokenCount":8627}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":8066}],"thoughtsTokenCount":97}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"new_page","args":{"url":"http://example.com","wait_for_previous":true},"id":"q8jik45z"},"thoughtSignature":"EokFCoYFAb4+9vtsenxLqqQxdEMBTS84fPfG1xsGHJ6JckbLR97g7mCgljX8/CJ1z+uvP6l7W4264EE9XzgkhYVWK40uD2/HSBRetjIAKPB2RgX0/fjj1xDNMUoQ78W2Dg3WUTyT9VFI6RzHkW/Fu9fKCRm0jGVV6zvEnXJzbITkRoxe+F/AOOz31fZwBczgz1+qCoKi1TKclEK/gVcXbsf9z36+Ufz1yoOr0gRlDO0UjaDW+G7uV3ojB70KzA1IK3bYcsSUGQJKJBzBbst8OPYPuNRQbhlmp7jub5wgT1yXChpkP/0UgNXGKI4E/dzCvZlefcVvZNE3LiODSy/yS/jqcHk3ftVneKsReikKxFveYoPPl0U0+gpt87HRpDqkrUuAyK3+5lDqXn1q7WRE133lc6ZCTVka1QzH5Ovd/L4nk9n+hHDSxtefwED6s3sNjbKoAdFedr2xkZp+Kjd0vNeM6ryYkc9oWumrcrw9lQbYqluDlDx7J29B9p8BxJSFdoUjh6Bkx0WTHR8vwXUxOWh+ptgZlPlhj7k9qzUNQXZGizwoSE/EduYLux8X6uok2DX3pTABOJ9Cy9K1soVIeOGW+KPtkQoCOgHkSZ+CHX6b5By0DQzgPyqD2m+vAl7cbIYaOXRHFoBc/P7+7FbTeUJl8/C/XvpJ8feXKiB9d48uf9NARA5NRsT1q24Qvmsj2cXGEqJlpWxUWhbAn074dApx+pavchReA2B1AF71DI8K9OtoEh1x0njUQ1Wlfysuwo/0rq7nkXWz84vE6DQ0V5YMnOn5RYxMUcgSFHpWhhGkwoJRTw2MA5VT+NjTTzmjCxPklsu018ZafYGci8mIfqRf663nBjulz3qxyA=="}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":8669,"candidatesTokenCount":28,"totalTokenCount":8841,"promptTokensDetails":[{"modality":"TEXT","tokenCount":8669}],"thoughtsTokenCount":144}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":8766,"candidatesTokenCount":28,"totalTokenCount":8938,"cachedContentTokenCount":8059,"promptTokensDetails":[{"modality":"TEXT","tokenCount":8766}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":8059}],"thoughtsTokenCount":144}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"navigate_page","args":{"wait_for_previous":true,"url":"https://example.com","type":"url"},"id":"chnx16bz"},"thoughtSignature":"Et4DCtsDAb4+9vunBo00wroGvRnP/utICWzc0KfwQAhL4ReRSPB5X18M7/F2wrQNz4jGT9I5S8hnAUJlpFqiJT51c4AodbVSzm9Xy1Z+yPZ8nl73dMEoGvDDNSo3BYBEA8nGpub4iemIFGHm1int/IKrtC2H7QnnnMfdtsKJHNInaXhS6RUHEC0mlSPBS15+Y6QDKDPGqbm2zI/AsRKNa+lQ4FtRyep85yKzjquQkLjh2f/NKDz+Ur8FQvGyOXAPwJF6xE4OpciShNrAilEaWmc/D0XJ3k7X6L2mrKjs3E1kD/HzaZNh7DrN+yc7sbwmKyfzIjtuD7/9RWaFwr19imxR1rmXafssWFxesYR+LVjek0EHMNBXga/4jbY4WW+aN/Fcr+/yIvguT4XGuTjq9aWkQ4ZjInKzePi623saNK69/Jv8qr2+gtOPeHpuqnA8R/Gm+C+3FFLDzlfkZHSiW4JFqrtegZwihyBxxKtNWy4QHGajrJ4iH8jh5O95nOLpiSu/0ifl6havb4gtiCf71hEH/NNAhv8WAcyHhcjfjGQTY6XzIYOLv7nFKjmszzcKwbqFqE4mX/CRjnKABJAx6WeZD5BkrOz+oXNJSonJmttXOjY10KOMvvrJ5NpWMTmpfg=="}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":8726,"candidatesTokenCount":34,"totalTokenCount":8855,"promptTokensDetails":[{"modality":"TEXT","tokenCount":8726}],"thoughtsTokenCount":95}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":8967,"candidatesTokenCount":34,"totalTokenCount":9096,"cachedContentTokenCount":8053,"promptTokensDetails":[{"modality":"TEXT","tokenCount":8967}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":8053}],"thoughtsTokenCount":95}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"navigate_page","args":{"wait_for_previous":true,"url":"http://example.com","type":"url"},"id":"dh6nh7uc"},"thoughtSignature":"EqkFCqYFAb4+9vt7hpma0fd/eZEmeYjbwxo6ESmIHiPzZ+MZPJfsSgUlrXp6Af/94mxXL3Nwc+FPDJ5JNaOhQ+/kFjYryOE7oXPUXb0ytTSW7zNxqFlUFVA55SaTsL5OIRLQ3uy9tUJogHBIOgWI7KXipmI1v+IIR8szhGyVCs8Ie+kAS7JczmtNPZAPXAFQGTnXB4zo1mRxTBennBrS48ptuFh0wSFRTeKJ+0LX0c8mh1QKd9yVoN51syU6Z6Q9T557aelXF2KAZFgR+daNvZW3dMTE5FTV9iQR4hJlrYu+eyukPIBljBUFBP+5wDcrs4lWVI2sfOlXDQE1yfZTlOGSCBQYZ46XbVdAF00Vw+seeulRvEgo8s8ocmZdn5+yRqgonxAJSiySeYM+nIscJA3YZQpuZVdS/SrHLO/ilIEV6jyg1xrAc7Rcy5gViENkTI2MZvSFpyVxr01vO+WbCiRavEbdaTppGoGcSJM02qlPwbH2tGgXk0ennSd6AbNLAHX+9QkkCjb6tQc6f8nv3GHa3U3Tie38I9NZ5e22gn4xZTpgZ3iWeGN5BNxO1DnB0HO3TBkf0g8jMMOt0cSehW37/603ItlOCwCpUjWr6d0g88u0HXsRHebdxqDWebHs57RchcYteKda+MJvmCxWgoB4GT3xwa+lgkS+MSLIQgjdEAXHnUJ+iTWixUpnWsm9DQYxPy3nylpUD6Ohmu2/kw80NoVLL6dD8oCDEVa255EqJvTCfTUMvOCrLpFVObto4bLAdfkTBj/cyqdDFeWa+mqnt2g/GNryWW7JHa7SMBplmcNIGMjMa5FRalko0owgySklPNbMzmc2w+PXwxjaezEzITt2vdOZ3GZGpio8DznTfPdLcaDXLljhYvk8KVO7Ex42J9WJiFxRsqrp"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":8823,"candidatesTokenCount":34,"totalTokenCount":9004,"promptTokensDetails":[{"modality":"TEXT","tokenCount":8823}],"thoughtsTokenCount":147}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":9159,"candidatesTokenCount":34,"totalTokenCount":9340,"cachedContentTokenCount":8047,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9159}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":8047}],"thoughtsTokenCount":147}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_snapshot","args":{"wait_for_previous":true},"id":"prantos8"},"thoughtSignature":"EogECoUEAb4+9vs2VKT5xUS9DU5U+/45Ty62Y8FY+X9StlepMysDdL7n3ce4fR7jA3v6MINt6Tcd52eU1+ZO5t4eqNwOOB0kngJ/QLVAFl8Izm1J/+D6Mqs+U3gSRJ8BsRUxBXdBFieHhNuQh76nPL4rEfBWIRlusG09H8So9++loPfjlSX+9Pcb99sG3RdVT2ZXQg6GMq15x+iGr62tz8DZCB5xnTEPE3LfwhJutlJtDbK/kbzOs7ureURbR1MQ/3/36Igl8mii3DV3PIIPIeo5CuByB1Ha6aJ/JWHV+ssmeBeE0oczCt7xC5VhHXM9j8JgUKx/sKrC+aecYSDe6oAFSpoUfMUUVPcpzTuIp0WoW5YDe9wvt/49o5pwWgRcjIlWdpYMrM83dMIG1BvD7qNU/OCsK9m0bxd40nvfmtj0PQxKMHDtBHw/RfvM85iHNrzPsHY+ZcI6y79sthxxoNdBllfgHBDi54EDSl551jgnAC90KoozJryBMCP5ZKWPgtsPoXhvbNOnyz/9HlME7p31RbA03/3NwjS8V01G8YDtG1HrmEZKtlrIW/G19DjBVlhoEuYpkKUmX57ck0rbxW4egvwDcQjcQX7QaJIWwyIUvhseImIObGLBxVuSBP1Jjef9nelxWG20k+BafZbHVg1jMPCiKJpreD7relu4L2Y0yk8ChGG3Sx2luw=="}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":8920,"candidatesTokenCount":18,"totalTokenCount":9060,"promptTokensDetails":[{"modality":"TEXT","tokenCount":8920}],"thoughtsTokenCount":122}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":9403,"candidatesTokenCount":18,"totalTokenCount":9543,"cachedContentTokenCount":8043,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9403}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":8043}],"thoughtsTokenCount":122}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"click","args":{"wait_for_previous":true,"uid":"1_12"},"id":"r79qt569"},"thoughtSignature":"EogCCoUCAb4+9vt60822Xp/P+yQ5WNfvAKPF0LS6q0a47hBm0WrzbxwEB/HBzw14/ywGVT0tGNCypHm6ZWVht+ZgHJoxA2WtHDOdRbdjsvvmJaIBIjI3wPSiVc2dm2S9qVKlCER5uMwhW5aHbgmax/F0TSHej+g1P4+uOq2cJeAZ3iqgCcyROYji/O/eKWIBqaB6Wu10KtUvj0M9Dd+K0GkPpjNIvw17IS2jUxu/yPPZ9m6CSSFrdhaA7R4YXaE4L6qISdcdBxgsiSRkI/m+0f3J2/1jPk24y6GjrWEiejKCpfMw+sHTnTaEABh8Unih31eRkR7/WmcyGu9/IUMXREEo3W3ddIGFgCS0"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":9210,"candidatesTokenCount":25,"totalTokenCount":9275,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9210}],"thoughtsTokenCount":40}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":9815,"candidatesTokenCount":25,"totalTokenCount":9880,"cachedContentTokenCount":8041,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9815}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":8041}],"thoughtsTokenCount":40}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_snapshot","args":{"wait_for_previous":true},"id":"3f6suk04"},"thoughtSignature":"EpEBCo4BAb4+9vvrysVAn4EZuigwkHrOkiDNXb0u0bGnAPKivQ0/NoR5aEg2NSa74y1Llo7ifzocwWsiqG+q0erh7yiacc0ypevnjpkJ9nfDrNOVoGZIz+OusBXs3AqPmMaNOiFCYFgOnNXIJNiZwkNSjrdSR7yr9T1L/+MfiXSS8Qy5ySSrlWyjjY/EBK0/vp4VHg=="}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":9251,"candidatesTokenCount":18,"totalTokenCount":9287,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9251}],"thoughtsTokenCount":18}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":9896,"candidatesTokenCount":18,"totalTokenCount":9932,"cachedContentTokenCount":8034,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9896}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":8034}],"thoughtsTokenCount":18}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"click","args":{"wait_for_previous":true,"uid":"2_3"},"id":"4hcr39ek"},"thoughtSignature":"EpoBCpcBAb4+9vtqGpRZfxSRRNqP+Yla/FpN5SRI6mbuvYI3+uUz6bG0Z3yxd6JeCbYM0+JCfyXOCbwZzRrdyf+cGGs0LjFlsRNEHbgjCzoGhN9dZdFzbHvFuXZMwaeCKP5BIWKtHjCPrtEwfsBO+HUnUqV5XeBB9zEfA8fgoPZm5y27ip8eyeYKtQarCXvaX9nIqygKNqGzxb/L4Q=="}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":9666,"candidatesTokenCount":24,"totalTokenCount":9714,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9666}],"thoughtsTokenCount":24}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":10329,"candidatesTokenCount":24,"totalTokenCount":10377,"cachedContentTokenCount":8033,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10329}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":8033}],"thoughtsTokenCount":24}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_snapshot","args":{"wait_for_previous":true},"id":"a7323k6z"},"thoughtSignature":"EoABCn4Bvj72+wrxMO/2uA1RwpvzkuM4xNaslnr/+xfZy/iqwCHZyR5bvsFfsovWYzjzolMLSmvVdGYx/bw6F1ewM3LohjPmEXdiLFI1fEBxNK0166QB6XdOGucHJbItjxzekY2hTYcMJTZWLArFaCl5+wxhLWXAE/zkj3TInmRSPRc="}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":9469,"candidatesTokenCount":18,"totalTokenCount":9503,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9469}],"thoughtsTokenCount":16}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":10156,"candidatesTokenCount":18,"totalTokenCount":10190,"cachedContentTokenCount":8022,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10156}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":8022}],"thoughtsTokenCount":16}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"complete_task","args":{"result":{"success":true,"summary":"Successfully navigated to example.com and found the page title: \"Example Domain\""},"wait_for_previous":true},"id":"81koewoz"},"thoughtSignature":"EoYBCoMBAb4+9vsiO1ru68XCJ6gZ+FKthHSwWAdj1b92U7qX4bVZ5j2kaNJFs3EYNsAdZHtkWLKLugPhFT1j9DXaptDYSfnbBbY/gEH5PW0RizHMI9VUQ6rvAyjrfUNh5ga7RuIEeRy3y0uCrglSx0JCOj2gtXG5kwGJDqT6YqTKjXvk3R0DRLg="}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":9606,"candidatesTokenCount":45,"totalTokenCount":9665,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9606}],"thoughtsTokenCount":14}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":10309,"candidatesTokenCount":45,"totalTokenCount":10368,"cachedContentTokenCount":8017,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10309}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":8017}],"thoughtsTokenCount":14}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I will invoke the browser agent a"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":10603,"candidatesTokenCount":7,"totalTokenCount":10649,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10603}],"thoughtsTokenCount":39}},{"candidates":[{"content":{"parts":[{"text":" second time to check for links on example.com."}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":10603,"candidatesTokenCount":19,"totalTokenCount":10661,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10603}],"thoughtsTokenCount":39}},{"candidates":[{"content":{"parts":[{"functionCall":{"name":"invoke_agent","args":{"prompt":"Navigate to example.com and list all the links on the page.","agent_name":"browser_agent"},"id":"rn9rpbw6"},"thoughtSignature":"EosCCogCAb4+9vt0QmjCmkrGoIi1HLtLRARGxmKpP3a2ZF8HrZLv4bfHC1a7d5a1BlPllShIkkAjL1RZmTe9tjpulNV7xsavrWINRdkfrNqC/on1h+F3nhQAa3HBbf4AWWH/AHmVPlUsVDr19hq4NLLL3hxFg04Fb1YEgRDHrjWOs3Oy9SmzdG+MiWv5GFVUM6spjOujO76dKzHZGe7chmMsmE8NhjQ9c/lbWUoXBxJ/72Qs+mRQHpd3p1ufkL0UV8bFtfyJdLTF4iJ/R5kJiqsEN1FtS3PpQaQHsmby7ytEjPX29ps7xp6NO1i/je9p3u+tGXEpnomqdJJ4SvdDJm4XyqO9HTIQxC3jg7mi"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":10603,"candidatesTokenCount":58,"totalTokenCount":10700,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10603}],"thoughtsTokenCount":39}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":10683,"candidatesTokenCount":58,"totalTokenCount":10780,"cachedContentTokenCount":8119,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10683}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":8119}],"thoughtsTokenCount":39}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"list_pages","args":{"wait_for_previous":true},"id":"vqfkekqm"},"thoughtSignature":"ErUGCrIGAb4+9vuVZfEoGpZh5UgzOffyNf9rzbFR/OyyQKQAOC8OtObFsJJvFgYIEXxBrnqRr8ijpJpQN0T5ibim+epO1Wos/ToAQwbioOjIxAdT9som9mndJSoBmA8D9HtxM6XjZzwf9qoMY2k2lUZjmk1BXRl7nFMtZ/cMQfFAkt2FUYZ64p2Y7xdY3ohweiMXFGbjjWvwCA1akSFa3JkVnfjHXlB3Yl9adrrL2V4h5OunJHbIqbyGxJPqBLyhrrhQbfrEtiVxVR09qqx2F2t3FMxXhMt1PmzhGB/2gAtlezUIvKdAT/c8h0Q4KPNueQP64UwxmKebfUmk6oDqrQG3sP2yF9fIcrqaiSv/DCecr/JYJv2E7Y79zDJV1FsKPKOn2cnpnmy9BgeXK0i2TCuaSm0XWK72EFHcMjqpBb/S5Y+U4DarGHnKGPd9YJ/I+D9NrxUNfJTmZw8wP6RQR0daiPX+APgdQOYXJOtLSXRY6lzS+oGKund7/hLhpHdcLto+UobcFEfNyJGG5+OHheKXCOqCWO602Rx4GFLTOYK/qea/R0QwFLgYZfJhjVIeeEsQvexxX9q/MGvtrilqqMEtHhAe+5QhamgUII8CcDK/15O7rgBbpQxXI+GPOJYMu5O0JMV7jC57DOeDCnslmyTlsqQtN3D5lhL1OKWSHjZmqHiRiSQGx23CdgNQF1ctsSKpB1GdXYqH9N+7TO90p+x0Wqbkfw0yJAF9TKHH9BJYciPSULjFcOLSFWqotQd31fTXur8eNv/fdk7kJZsfqxX50M41bBhwT4ydvy+Q1ao5Nu9TXRF6kGDtcdbeZOzDWZP+2akUH2glgqWSy4i97HG4H+UHqDXMpUtWzVFiJw0NcY6Pdt3XNbT5hO6mSO/A7ZhR8N4RSyw0Nh1eRzZSqu79valUfi+ykuB9s3w4duMmMNpGpHOMNVu+ol2ltY6zrwhFz5TCvo/zXmabLc6kEYu1C1xazAQLGFjxRVpcN8Qft4eN3KZRou5zy3Dp4PtQ+KiX3JfMb2wnuXWZ1CoUNfFnVi0Y5VpzeF7M8Yzi+ZarzcE5XEFSROl3VfxeVZyFlxgZObU83K0="}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":8630,"candidatesTokenCount":18,"totalTokenCount":8823,"promptTokensDetails":[{"modality":"TEXT","tokenCount":8630}],"thoughtsTokenCount":175}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":8630,"candidatesTokenCount":18,"totalTokenCount":8823,"cachedContentTokenCount":8066,"promptTokensDetails":[{"modality":"TEXT","tokenCount":8630}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":8066}],"thoughtsTokenCount":175}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"navigate_page","args":{"type":"url","url":"https://example.com","wait_for_previous":true},"id":"dsy0oh63"},"thoughtSignature":"ErECCq4CAb4+9vuQlylaFlrtKtYp1EGoDqb9nhSmJ9IPYvmFcBku92rqL1WJU3x2CeLvj0tww69yEodZQSSIJw/4hPtWzLlr3P362ITxFIgvgpzAue5AIjH6EbXJmAcEO8i+I8TV8QneJQiula5HBUdncCTqKycdrjyCKVbAcV1nnkNxbPagAiuumgOaWGPPmE4d+W4Xgbq695Rov0xR0ijSeknqkcf14JKqZqpyuGdMYSDxQhveQS7o4UgrsUhQOgEVc53XqfV5tMjhC7Agpb3WN7zoDUmIAzgbnoG6ha2mJOQ2x3jSRUozLq6fIQ2oZSmb7eajvcGqUxtXp4n4z9cdLpBuHlPB8hfiCxNN0RP60uvaqBtvAFBNfVCbk1eZdxnnvpwQdqp3Ue7H4lzJ9tDBey8="}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":8672,"candidatesTokenCount":34,"totalTokenCount":8765,"promptTokensDetails":[{"modality":"TEXT","tokenCount":8672}],"thoughtsTokenCount":59}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":8847,"candidatesTokenCount":34,"totalTokenCount":8940,"cachedContentTokenCount":8060,"promptTokensDetails":[{"modality":"TEXT","tokenCount":8847}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":8060}],"thoughtsTokenCount":59}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"navigate_page","args":{"type":"url","wait_for_previous":true,"url":"http://example.com"},"id":"spfjmlgz"},"thoughtSignature":"EqIECp8EAb4+9vs3KbfYgD6JCpaYYtFFyQdv6a7TPEL2cMQCTkaO/7k7goC17ok0NkX13S9xoQmLqXDqDwMnLZd4BcJVZoqeBysjLEOKA6AcP7ZCFPX/84VTtMgvB123l2x5GcXq/9yDmSLqy9B4U+u4cEtdfArQUuFWPh+bGqOCdxlrncTv6c2yReLXSLYUiQMNcV2f51g+giKIeG6Gsdp/RQNVTi1RrVIun4sY6t7Pdogrp2hGXP1VuQDCwzyuHcVPfvVh8oMKdPUywrdaJvzH2/L4F9OoNpmfJkjJZtYdPLFzoDm5uzFV7jNiwvYpeL1/EnpB1nbh2in/i3Q0MH60pf6ik7r4ebcdA9FQ0zjQoRxH3zqTYIH1QaeokwpaHkfKNj/8+Ke1mmmmTf8XNs/cZ4t2EQO3Wp/SVtGbIYq4LvbdZfMhIpFX1dHlAQlo+52xffABzfkqki5Rt7V4+V/9FpARvkBX3/lmK/tr7aVxsne8CRv6JKMtPbjnkKYhm7IeKKt9tEP9ZGDJ9+0kUVBmxnWbmn0cO8PE9vpfrISEakjC4H0YuLWo/ra56t8MJ6aUJNjTHkN5nH6cQuIB8V5vOne534SKs8O4tM7StnYJxneczLe8L6cpk2Wp4W4KWter51N/sr5xpKbo0lN5nq13EoXGe0cSAN8ze0O8LNMmPjrfDcDEZfHlsQPvmwO+w9A9nBU8ObUGDQddKaznrbIhSafH"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":8763,"candidatesTokenCount":34,"totalTokenCount":8906,"promptTokensDetails":[{"modality":"TEXT","tokenCount":8763}],"thoughtsTokenCount":109}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":8997,"candidatesTokenCount":34,"totalTokenCount":9140,"cachedContentTokenCount":8054,"promptTokensDetails":[{"modality":"TEXT","tokenCount":8997}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":8054}],"thoughtsTokenCount":109}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"new_page","args":{"url":"http://example.com","wait_for_previous":true},"id":"xa6m1ifg"},"thoughtSignature":"EooGCocGAb4+9vvfzf5F8NnOVWM1Ge5dkpHWzgAx7E3Tjv2BgvQHBaPEtUN22F5ps7pVx8VvtFR773quXuQQFnjAVSgKK7//8P561JvXkK+nl7rh1s01RDLHUE92hemwAcre1xBftHfGlLADiPg3hXKFLyZpXYMWoy5+RY2Vo3FR0jIG6rpThHg7dfb1OhOLGP9d+hgmcpwozn1UVle0ncg/mVHd+uK6jEKt7b6IZAbyNBLUykYPoxCZKtbHYuZyOL55HksF35CD3x4AAn1qOhV/tARjLEWpxLsrnO5ten76un2Guw8pnsFCB0SmZye9Qnr5eSfYc/fs6GE41dRfDyc3+BOSlJWjoxxyRzNfQjAfS2m0SUQ6o6+EtlXqkjCfNnR0moA3HVYTIXlf1QMownc6HOX37OswmwaVf/YfVGxfQM02ysWycgmapGefKkZim4qmdAHqZuEwXn5qCnG1asbPQSLqxIOpBrEEZsiCTpkvtSd3+4oED0jBmEq3x4IU8uW23ujPcsrwppmwLSCyPYx+09xhPdHruywtGAvEhRDzfHYSnW3ZTiu0ZBKKRmPwcKMBn37Z/GjjjeI8HGPCVtP5dbB0vlTsNuNe8lYSWy3H3dOFUIqfsATu6by9QA0SBmvN8/CMgkHlAgofrbhv27XOIQMFzamUmj3bsEXZR5RvCbQ5T0OLM+4y32B0VT9lIhSLijKDo1XaoQ1Erj66RIcv3jvrseTmIFTr8T0tkUkOqir7p1udfbtxWh66kHTd++JAevEJC5HZcmC7MmvGux7gK3NRhhteJ4qOIM+KGW17BukpOOcJxBH3rW8tEJalC30WdqoMM+TBKbxVqBBByU12TeGnEYOxghxnXmPhMOzuwUyTqLUTBCemaQuXowwNArJozzewcw/uY3lSsu0lEy8YCC+BARMmh85jT6JBvvMMThKcNhtul4gBIkOfLDqUjQcFvMs1QBU+kpQP9oHpXluxkY5cdANPG1cU94GJN7PoycJcRju9Vf2Goej+P6ZE+BkPM3f5lQQqBgiLLg=="}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":8854,"candidatesTokenCount":28,"totalTokenCount":9044,"promptTokensDetails":[{"modality":"TEXT","tokenCount":8854}],"thoughtsTokenCount":162}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":9197,"candidatesTokenCount":28,"totalTokenCount":9387,"cachedContentTokenCount":8048,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9197}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":8048}],"thoughtsTokenCount":162}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"new_page","args":{"url":"https://www.example.com","wait_for_previous":true},"id":"1pxqeqmn"},"thoughtSignature":"Eo4FCosFAb4+9vtx8s8Eo2HJhknUEkVg9w/xZwt/pceuH9eggBkeyIgJbskQCQ32xHqgSXrz5d9p0tiVjYrPFA6h2CVfQP2GFJE4BGGG+QD2cHMweol7asWRXQIM7NiQF+asQE43IYEhBl0EQ0QM9C2/kiQIMyCmXVF6N643ZBjwHFlM72t6spatjj6+z2Ep/B8MycvQoFw5RAvVegJsu1O+Ep8yCZNyCUcx/2Z4bTAt+Grnzwl0o9QI6TDg7kuNWCc4AFnbOGRofB0C5emwEpnDaDH7mS6TujFpoDwoCawuEUYQm5geS17rraeRdXB02uqjadjINgxhDCWzjv/yehLGIYL09KlhsA828Vs8xT8dz3TWaHJB8dS2xAxnL8qHd+Bp6geWa9SRpDZUDAJMfpI2TRHDSba5i1yuUyLFtDdRFEgBUweyupSmJzziG6W3Bhu+F2PF1FlR1/ksUUONqxMOWMqVdJTLg+a+nXlPcR1KOZ7+3JeuIbZTRjZV+ZHm2XBQ1kyKCI51kXepPnwQX8bn2Wo4WpkDk0S4y1USHDhpd9hRslldl41s4YCEnH9P+D70JK++Y2ZD4gQxnSdpsEAUXvRGw6q4ubMKP9XxMyaIx1lhDy/g1/f5MG7qP4gzX3O7Rz4ByU3Ghm9HqX7gn2aXjkjqjr0fM7tKPRAOmzOuTObVm+FKYmxHy1VbnoMfDBj0OaTuKPkTKsvkIhXg3sTaP8soKAwtXj5UYaZ3KDSECjuEyb4+W7N6QEfwkuYUAnlbPcpgARuzMBo8Xjysu8hQqsyoWTqHW6fjI7LZ43Ykj9wqgaokuHuIB7rm5UzCjbEzKmpvPLSrmtDTwiSed177A62h6si0AaW0BcXyAuYS"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":8911,"candidatesTokenCount":30,"totalTokenCount":9079,"promptTokensDetails":[{"modality":"TEXT","tokenCount":8911}],"thoughtsTokenCount":138}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":9416,"candidatesTokenCount":30,"totalTokenCount":9584,"cachedContentTokenCount":8043,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9416}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":8043}],"thoughtsTokenCount":138}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"**Clarifying Domain Inclusion**\n\nOkay, so I'm stuck on this domain thing. I thought I understood the instruction regarding allowed domains, specifically example.com and potential subdomains. It seems the domain provided, www.example.com, might not be explicitly included. I need to be certain whether the original instruction's wording covers www as a subdomain, or if the lack of \"*.\" before \"example.com\" means it is not included at all. It's a key point!\n\n\n","thought":true}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":8968,"totalTokenCount":8968,"promptTokensDetails":[{"modality":"TEXT","tokenCount":8968}]}},{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_snapshot","args":{"wait_for_previous":true},"id":"5fmzjlk4"},"thoughtSignature":"EvQKCvEKAb4+9vv30yZpPMefi7d59ylbOlqo4l3zUITcJFaN2TTkHVNQ3CdIxDGsx5Dg+S5zyakF5xi8Zn67ye/SAG5lA1TalA6GcEy6ukrFQrwTCcYfqRqJv4mA+Pczi+ugSu1SBmsVRJQ2+kAmU7SLapEgUaGfm6TRh+hjougIpe19FKu0/1eln0DM1Fd0CL1mUWswwMFBt1ZkkyTLF8rSGNNQM7mNG2k8pxj0XqIzulfFbSuq+1hE5SCTsETTdCRf6OpeYgUL0VhhC+0uvtJsdE/PDLLDdwve94nNbqzcG1C85c33laM+DooXpdTvAjlZi8kaJqgcM9dAiRvIJKmtGy0NBkViSQDljLw0WwgzxaPFN18ivLdoxaCipI/m9zDyO6mWvhzzt7w2oF43FEUly0B7rjf4fIOkyq98URLpmW4wYn7r5ZwbZGhiIzDZhD3mXJvkvmqBRL7v37w6sqFSl0nscLXCV+DHgQ0AjE3I3z8RoAHBus0fMqi76gMs1YY9atCzLg1/f1BsBuOx2Ev2sU2Jkr9SNMnxcH2f1WgGIvzJYYt4rZ//hnYIRYHqY7IidID0VZOq5KQG8OWr4n1SM+RP0MkGsS77xL6yhJFHjrFfPJJ3R1RD7HfSvpJsdftuttTjnraEjiJALsMnOo3FgF3MnXeNdOMLHxkjQq4czgVOwdaCq/UHFYPJ8Zug/L+7SZFwjVp4uuIrM4BNnXbs5/tZZZ9KGm1PBBqI/OERfcYjKhpA/xD4EHzLCYGxJxEYHSUfqRVMvJOQme6A0cyRfZzYyrda5O7P0pNtG6/4WhoPC11+PYyXBuAuMlxpUwjaOTaOwJTit6p0uy27h9bhl22LiSKf5ylHEz4jEgOYUD9WJVucot2R43L1j7M1emcRpcyZrxRB8tKkA1XzXAH/m+l3QbCfJWDEbt48muTEqGjUax0Z9Ft3iJhDEZCntg/5OR43HNpHtLPNftUjyeUWyh1uQOAUSMxLbG4vHISFedkt8K52LpGi0wYhEVTyRVGjhJULE9QEw7AQ5mt3DaBCZN1bfIRvpBz4efvH/z6NMeE+3ohgFoCRTo2iJ+dsshwDbKn05KZTSh+t/oxX/xzi332J6Jhvu/PiEWKsrUYdZnuigPogVi4l2pss9L3wrKBWB7MQg9tOSky1kRMGM1beFYIWyzikml1hx2zKQbHaOfpfL9xW0MoOILfV/ihe+WNbiGiGthj8NKTh/YrAnK9hnrHeSgQcB9v8L0aeit+iwCxoXV4gF54xb4CT5LACF+iW3YOGD5z5vc+HXcLtzFX+GfIgm8dbRQDwTXEaXIpXyulLLtRqCEiFxbe52wDT+e6CpXS3r4Ulsvvt1faCsf77eIj6M2EnugH9wPDNuRCMXkPff5P9F08RUobz5V9v9BC9mYPGRPTkwST/bZsFSz8MHHXUYPNgHkI3tGAi82R36XrzMDNa/0c1ri1747Mc18qStloO5WTnABXEhEO40Wourh07CyMSD8w6p/SHhepW87JqZ3oDwxPyP1Fc512U8Qsnas5k/z4IxWxoIQFTooG8omLHLI9vfnZ3FDpQf4IAgDsG4gsvrb/EicMFqF4EwO+crcW0oudVpk0a2KycBytniisXSazITx+nw6gO3ZSvoGsgrudn75Bb8W4KoWeIKrNi/iKkPrdzmj7dC6JcmpylPfZKr6r9ESwhFGEZIK/JaPK/MxkcbL0RcLA0ugYtY5A6uOZJTCCrTMV6h0FfoxmTCTagjjZhWyU59K5BNWOOrIVXJRd0XEa+Dh6foFvnQD7Wgq3zRG2BwZ0QQ7rFulcpqs1bLcPkGU8vw/hbVQ71q76OYh3ACz5XJdgOPSpk5M4+5SsMQQ=="}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":8968,"candidatesTokenCount":18,"totalTokenCount":9293,"promptTokensDetails":[{"modality":"TEXT","tokenCount":8968}],"thoughtsTokenCount":307}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":9611,"candidatesTokenCount":18,"totalTokenCount":9936,"cachedContentTokenCount":8038,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9611}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":8038}],"thoughtsTokenCount":307}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"click","args":{"wait_for_previous":true,"uid":"1_12"},"id":"w39u7j53"},"thoughtSignature":"ErkBCrYBAb4+9vs4gIpgWxIubEi40hUOpqyqvtfJ8qAW3Yz1wJHpce3xFhJwbOc/wiY7AIIvAYAjG0e5+/Y1KrlsGbrUrTK1oDN+yOvei7feK2qXLJVNArii8e7n2MYmgLJM+LXH67m/fxTR9m9rZ6a2RzGpFkjKmXfEk95I+Yx2HYam0DrWc2g/mm3tkXp5FcEG1jx7KNBTv+/uLC/0wR11Emp+vR6tEhtbcv6AIbqqVc+ptmr8HWuZIys="}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":9258,"candidatesTokenCount":25,"totalTokenCount":9310,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9258}],"thoughtsTokenCount":27}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":10208,"candidatesTokenCount":25,"totalTokenCount":10260,"cachedContentTokenCount":8039,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10208}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":8039}],"thoughtsTokenCount":27}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_snapshot","args":{"wait_for_previous":true},"id":"qzaod8vn"},"thoughtSignature":"En4KfAG+Pvb7Wx5fC3gw1Fn9qKx0pll+kkEOEIlaOD5/OJJAFZvLbkJkxdd4qP7+X6FxWf1vFrn8uoWUQxUCK3y/E1KTOMDEKozi7ApeB5W36/iVvHI/MbELdTDQrk4unKqMvcPTHDR55qHDVcS25FyIpqCHQL3OiodSKgK5//0="}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":9299,"candidatesTokenCount":18,"totalTokenCount":9333,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9299}],"thoughtsTokenCount":16}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":10276,"candidatesTokenCount":18,"totalTokenCount":10310,"cachedContentTokenCount":8032,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10276}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":8032}],"thoughtsTokenCount":16}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"click","args":{"uid":"2_3","wait_for_previous":true},"id":"izwyu21z"},"thoughtSignature":"EpgBCpUBAb4+9vtf3WpNBe9YcImIFf1HzOHJQ6WIKuadA3qMRbi5n6DAl9v+HkdpYL1tB/5bVGJJ5yDhoTjgAPuMfzk9H0DS+T/NoQjwRBPS6pMMNdVeY0O7Qckwc1oXVzZEMO1mOLHxItYQBeEhQIRme2/ISq6ObWvtEAP/2wazYsPB9m5+suOXNVQhtv0lAtgFwka/O08mhRI="}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":9714,"candidatesTokenCount":24,"totalTokenCount":9759,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9714}],"thoughtsTokenCount":21}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":10707,"candidatesTokenCount":24,"totalTokenCount":10752,"cachedContentTokenCount":8031,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10707}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":8031}],"thoughtsTokenCount":21}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"wait_for","args":{"wait_for_previous":true,"text":["Example Domain"]},"id":"u9v4ngts"},"thoughtSignature":"ErwBCrkBAb4+9vtgNuXwkrEU13cJ58BYZBhnQ2xlGVufQgMl26Hbwc4zUciWIrWI+SUaunKBgnut6tmnGA3SYJAZ0wePJLnh3bCRJBcmX0aNEsResAIXD0nmC4tcDQnuWuFHNeb8XzfVxiZcBsnjqNVcZtQ7rT01GAKzOKvfIYKhfoFkwv7spv5A6LUefkHphnu7+5BJYrpBvEN+5xzOZmlNXuUprxVsfbOnopzrPYDZ7YrWZr+R6Y5W0LJ5afI="}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":9517,"candidatesTokenCount":25,"totalTokenCount":9568,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9517}],"thoughtsTokenCount":26}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":10531,"candidatesTokenCount":25,"totalTokenCount":10582,"cachedContentTokenCount":8021,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10531}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":8021}],"thoughtsTokenCount":26}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_snapshot","args":{"wait_for_previous":true},"id":"en5wzv6a"},"thoughtSignature":"ErMBCrABAb4+9vsFi8H2zIOww5bzPlC6nM9VohFXg2gw1IGPsI9e8UzwMm8f6OpzofLx5b3+W73OYCBaGJD0ubRJeIP4yA3kjvaSm89FyjQg/oc3Ktu2WosK2nKynOzk3FoLN3fOMfMWfxKOxV7k5TBnKwGhnEn0WGGaKF2YiAap5ADC3KCIWP5dsJKRHP11AuLlvTvvgptxX1WqqsyXMijvLXbnrdfETWI6b7LtOiUgsTHfS3Q="}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":9565,"candidatesTokenCount":18,"totalTokenCount":9608,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9565}],"thoughtsTokenCount":25}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":10605,"candidatesTokenCount":18,"totalTokenCount":10648,"cachedContentTokenCount":8015,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10605}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":8015}],"thoughtsTokenCount":25}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"**Analyzing the Links**\n\nI've located a link labeled \"Learn more\" pointing to `https://iana.org/domains/example`. The snapshot confirms the link's details. Now I'm checking for more links.\n\n\n","thought":true}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":9702,"totalTokenCount":9702,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9702}]}},{"candidates":[{"content":{"parts":[{"functionCall":{"name":"evaluate_script","args":{"wait_for_previous":true,"function":"() => {\n return Array.from(document.querySelectorAll('a')).map(a => ({\n text: a.innerText,\n href: a.href\n }));\n}"},"id":"2lza53qd"},"thoughtSignature":"EsEKCr4KAb4+9vsWUS0PkhIa2yvkC55YPdwXqKNqo3xIfQKKzxDHESpY2YU6kAxaRkt1zqhfYpPYu/huUGWy6kBrgHFM44wdwN+FEG1zpNJK8HqckU/YhT7oXzZxHxbmZJo2haXL5OWUY2Uwx3TiDfhhp9rf6zk5Px+rD9dz7CuxCzIGcX4mjaidEsya/1q4kWs7k2UtSOseWxLHO5TYXHrih7cDQtVj1Jl3Fnw+ERjPKDb0vcz27IrW+GAmFVLqR+F2eNrtuKDdNLs2OMy+FtUrtv7PLFBym9IzX2JWNRJFl3vuyaLjEymnSGzvvwEFyRSvN4Gj6TK5H/Q0yTtcHyzP+AxNvxz2wkDwAm7zm7oMx819hnK3SrWCudvywY8DtXw0g9ta+jJziV+nMFM74osTeO05NKRnvSTBgmnjlpdI9R6OVXlxnQtqvSOeyeK56H9/OK7mpDWMZDqJNMvf24yUFLuw99gUm2rLeA+XhCL2RS+bha8QMhdqp/4x+c5ucGVbHKgDZ28TCm6TlKaolDuSuL5yfBuZ2iSV+ZgicCX8Mlqsd7yHBuGv3Db4pZ4XfjC2omWX6g4usQRa8O/lYd93bXWcy/yieJfIkXwk7RHYRmst4ZqholBteXtUF4yAkniwNGdgbJcNnsaNA29rQlX3mD4/L/dOsgAXO9tsMq7JzwPFIekq+Wn0zJxg+aAc2FWaczqi/a20fpNbRfBBWuBjfiYbadPCpWAwyGpXmZt7XUUPSMkSzjfolGXCiJlSOwp4jrWn2Msmsm2mXkd5/gqjNoSK5LV6gBjkiapD1949glo/rDWwVj9LC3j9C+m+Mqzg0P3qk2kIK2aSWToqpWCQluhgZgv1BJt/VyLdSDMtNZj49i7Oue+22PVrwaD7r0exLfS9mBRKJJs2pegZuMfaDKWycCUBQfLGAJhzKlBCXDAPmtFKx348h3JaRyTT3skpfSuKb8MBgGjS0XLaVs8ZpPT31Yxwn9LFvifovB1cEprBvBmjuyCnSVLr6V+p9tJRi+7hVC1fbrz5ntp6DFcxIfebmPLfQGgtUa1evlfhgzkkjEq89qPxmRbFNGtkfZfeD3nX3XjJIbpqdc96X2Yh3L8jvW3XxtxBeV8fuqgsZAt2FaURh5WXaSLKxhtSt5mRRIIlSVZt3HijQjkfQzUqUaC63sD5WKE6tmjhtBdI5aZPUgM7997Fr+lr9gqS+9UEy4h8hbf9x66k6daFuyVBSPSmF9T2S8WHIZbAxtfyx/npxn4nh6xFUE/lIX45pVOQlgsgvcfzQX271syjpOTXqz5wJDC4wKIM5raW7bQZyokEijPihc28c0nELGZfaU1tMP0fgfGNlWNXNQfah/+HfIglZlc8PIoRNfzuZCCesXMwmb9sIEe9tRFFzIbfjf1PW3rIb0Gnjwy+2epZfNkVc06L1wv47LHkNIBbkYCakrcCPAYRbSvDZAKuH9Vzkip+LzpluZkF+Hdsrs8gY+3i0zAc02pnZjkBOJeftxcVRORSE1CgiW6FN9XNmLsbBEpEjhWRgYAfciv+x2WOwbPpJ+fRjyQz62JgQpkvZqegNFpInv4mDdg29mk4MmGg4EM1oeX0W5VG4T8/0kQ5wdlV5h/mOHINqR7kkQ4Bm25ytLuOLssSyMFw5avlPMGZRwlt/Go1Y4LTmDQRgoRg4Ec8+uWoePSfn9aLnocwUwxK4SAs4crKnlADI4wXGiG9H/Xo+SGvOcfRLLwAzoWaoyb66p3raNaVcf5IH5cFifejn8Au/98eWRaPhKJRIhBddfaZUw=="}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":9702,"candidatesTokenCount":64,"totalTokenCount":10078,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9702}],"thoughtsTokenCount":312}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":10767,"candidatesTokenCount":64,"totalTokenCount":11143,"cachedContentTokenCount":8010,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10767}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":8010}],"thoughtsTokenCount":312}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"**Clarifying My Findings**\n\nI confirmed there is only one link on the page, the \"Learn more\" link. Initially, my process was a bit unclear, but I am now certain of the task's instructions.\n\n\n","thought":true}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":9450,"totalTokenCount":9450,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9450}]}},{"candidates":[{"content":{"parts":[{"functionCall":{"name":"complete_task","args":{"result":{"success":true,"summary":"The following link was found on the example.com page:\n- [Learn more](https://iana.org/domains/example)"},"wait_for_previous":true},"id":"1lqryjfq"},"thoughtSignature":"EqcLCqQLAb4+9vsoMELp6+SQ6kd9D/ukBfYl5MDkq0Jxufu/S3yvYHVsVlAcRFrpZb0HYZY1aIhGDLZ1JaSK680/g305h6UD7WKKuZAGHBBcr5GyNYYVmCpY8H39kTqdFMkcxtxYjPFB6lcZeTjLHGsMVmat08+PEE9caApuROHonZXlyZZ4wbmg4lI26b6h6LiB+kUAMAUAIN1WBexXEXj+gl+8HgSwqi/ntQYWa5WPpixNk1eiFQJmk5+0lFVrGxWoknDIQ+mc4AgvXbI794FvIrpdBwiQZ5wD3IYkU0sbUN239TiLp2MAYY6qshHeL0JhkTsjIG9GOwVWFft4BSHDu9pVQJiY1ApCo7rhDoz1CjFtjavoC090vffmKU2SCKzRvF2lhSFzxk4urWmn469GjuvK71GVAsRCsqPwBctXDbBsK3rWjLDNzqOZ9BHP7K/mQ0LwmIXs/Rs/CJeTctgC9lg1Q6eFgStQMOjP94EdudvH2sCjg46iM+U8RZXm1b63Zri7f/Y0gnUvGzb+IvtVSkj2yEbACBpwEAvICY+0meToSDHy6XDiAGjCintSaoAhu3/2kJZRh3INvvLrS2qtO78AUZWO6M5Pzub4ckKcuzwJPPGQr9Q5qZbRyzqdjlR51D9WujLdHVNMcusjUnbCO2sDWOxsUQlibU3GFQrhMJ7Ka4wKXWso4umPsRtcP06n+8ZbQXg9dl3yA/Qvg2wwv7hQ99tXXOKG6hRAPEEetkYxw1iQ7tFJaJciyh28pv606RfM8U/e/0ug/g+olpZJPkY+21W7Lj/MrEK6CRUd03+pMBXwUdEwPKMCM0DkQPi/7vRMwBk2cMJ8Yk8ICx+sGlnSg1Ff/z9XMDKEadXQcPJ4Pt4/m+R1VuJ+Du9WXs4dFE92FsQ/FGjduEYtisARIpTJzAkigFlm1+lzLUs+5ACgzK79Y+O4a8ATvkYmOTex2nJiuTNq+cCPLi4cMnrSQb8wPx8sq6cIJERrhqmo6C2dlel7iwuk/8CnJo1rMCwCtTbJ995aJAoEpRkiBzMF7WG7abFT1q5NvHJ2KQV64/iRrqZU/Tiv6I6iDswafiKmFbdTovc9i5ALsdOali3X8RCKkW2aWMXpKxeIOiVOpzR+CcCw0e9CTM4YU2dLOb0BlhFOFVxGxRBycInLCblUaFtM/ulik5xUS7glGRT/jzsf17igtl/+bbV/t76tcslYOjQScXbKJr3REYN0qqK2hFgxnhXoN9IeD/nOWaDFMP2VJc5N/h1gsIA0Ac9PSgz/jNB0XPQ/CrhgauOUacXQ3otwEJuliEagYhu9n01xJuHIZubZ3AuEJ5pIFMvtWuAKSeSq8McCPCJKJgivk5jDsdTKKu8rZ6/RjC+qu/NyGr0QYmmVrhTk/cpHihRT+x4IadkwBmkBS/q+DXeSlpML27YAXEe1jzsNeG7uzBuatUlZDR6rpumDGFCQBSUPWgBHWrWqNvrcQrCFtex9ZEY202JpJXVsQZRBAYcxQkknDMLU54+QpORSA5CW2E2ERUaBi1s1RmmixlOP+b6slJG+VwZ0xOHhl5FfaK4Or7c0E0tImttduArpRTIbODy/5OwxPY28fHxHL/VZRl0u63wJXSl/Tsl7hY0bagZguYrV+Xpx2O+fSMdgPG5Kj4qaYJVrv4Zri+UOUmwCT1ralRhbsbPXcvuHLMDDPD7uqvrK/ZTF5lWb55TtlmrUtiGnNqw7LS1vIGbgHQrRbV8hG5fG6MN0ooac7T1L0l6FjSJjVCPvqKTmle4UYPknTK9Y11CRnymcUP1tzThEKSnazM8o7CtKkZk6fAugSk6LMAgwLoCvaOvrVpzH78zilWPWuCSGLeh5ltl0dE2wDrb1gA6Ctbcm1jqX6jUuquOFz07UuogZlP6OqhIY6sqkm0o0Q+gkU4TwtIi/fA=="}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":9450,"candidatesTokenCount":57,"totalTokenCount":9846,"promptTokensDetails":[{"modality":"TEXT","tokenCount":9450}],"thoughtsTokenCount":339}},{"candidates":[{"content":{"parts":[{"text":""}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":10827,"candidatesTokenCount":57,"totalTokenCount":11223,"cachedContentTokenCount":8004,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10827}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":8004}],"thoughtsTokenCount":339}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":10731,"candidatesTokenCount":1,"totalTokenCount":10798,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10731}],"thoughtsTokenCount":66}},{"candidates":[{"content":{"parts":[{"text":" page title of example.com is \"Example Domain\". The link found on the page is:\n- [Learn more](https://www"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":10850,"candidatesTokenCount":29,"totalTokenCount":10945,"cachedContentTokenCount":8113,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10850}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":8113}],"thoughtsTokenCount":66}},{"candidates":[{"content":{"parts":[{"text":".iana.org/domains/example)"}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":10850,"candidatesTokenCount":38,"totalTokenCount":10954,"cachedContentTokenCount":8113,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10850}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":8113}],"thoughtsTokenCount":66}},{"candidates":[{"content":{"parts":[{"text":"","thoughtSignature":"EtsCCtgCAb4+9vsG6jAGgwWyEfGwT99Cs3RhM4EVEyjTlze2DxNUtsRE7mMenqfZcQie9h4+kTEKjQM20PXBD/IFsUIEozrdJbLJhm9CpVQlqG1yBh2q8q/43jRmb8A2T/JcjCr+3raGAOkRHgiBlTkdvcAAQdHhVa9S+WyI1mtHOZ/Caa2Zx9bgKFB7GEmHQud+39HwlwL8ZZMy/yfHNk8C1PYfwnEvvBGiqe2wL6w+BdMx1X+2uixe62VwwxBx8tLvVV7U+RTagCH8xLAl8AZUtFYuOOh9cdeNjLYtSmajVp5NQyybriDy5J1+zmmBW0EGrunTZ6YZOBI/NJaogboeuNrX51l4I1bWYHzrgY5yeeclxhYgYSPs/RcW0lxcJwIlH5FwZBVkVra14U/RvAiL8/PxTTVROtp//zZLNgEav6S+KEiTqEV7yzwd797WkDG9NIAyBX235WpRVTc="}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":10850,"candidatesTokenCount":38,"totalTokenCount":10954,"cachedContentTokenCount":8113,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10850}],"cacheTokensDetails":[{"modality":"TEXT","tokenCount":8113}],"thoughtsTokenCount":66}}]}
@@ -1,2 +1,5 @@
{"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'll navigate to https://example.com and take a screenshot for you."},{"functionCall":{"name":"invoke_agent","args":{"agent_name":"browser_agent","prompt":"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":[{"functionCall":{"name":"navigate_page","args":{"url":"https://example.com"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":20,"totalTokenCount":120}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_screenshot","args":{}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":150,"candidatesTokenCount":20,"totalTokenCount":170}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"complete_task","args":{"result":{"success":true,"summary":"Successfully navigated to https://example.com and captured a screenshot. The page shows the Example Domain header with explanatory text."}}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":40,"totalTokenCount":240}}]}
{"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}}]}
@@ -1,2 +1,6 @@
{"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'll help you navigate to https://example.com and perform both operations - capturing the accessibility tree and taking a screenshot."},{"functionCall":{"name":"invoke_agent","args":{"agent_name":"browser_agent","prompt":"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":[{"functionCall":{"name":"navigate_page","args":{"url":"https://example.com"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":20,"totalTokenCount":120}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_snapshot","args":{}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":150,"candidatesTokenCount":20,"totalTokenCount":170}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_screenshot","args":{}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":20,"totalTokenCount":220}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"complete_task","args":{"result":{"success":true,"summary":"Successfully navigated to https://example.com, captured accessibility tree snapshot and took a screenshot. Page title is 'Example Domain'."}}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":250,"candidatesTokenCount":40,"totalTokenCount":290}}]}
{"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}}]}
+142 -10
View File
@@ -77,7 +77,12 @@ describe.skipIf(!chromeAvailable)('browser-agent', () => {
),
settings: {
agents: {
browser_agent: {
overrides: {
browser_agent: {
enabled: true,
},
},
browser: {
headless: true,
sessionMode: 'isolated',
},
@@ -93,7 +98,9 @@ describe.skipIf(!chromeAvailable)('browser-agent', () => {
const toolLogs = rig.readToolLogs();
const browserAgentCall = toolLogs.find(
(t) => t.toolRequest.name === 'browser_agent',
(t) =>
t.toolRequest.name === 'invoke_agent' &&
JSON.parse(t.toolRequest.args).agent_name === 'browser_agent',
);
expect(
browserAgentCall,
@@ -106,7 +113,12 @@ describe.skipIf(!chromeAvailable)('browser-agent', () => {
fakeResponsesPath: join(__dirname, 'browser-agent.screenshot.responses'),
settings: {
agents: {
browser_agent: {
overrides: {
browser_agent: {
enabled: true,
},
},
browser: {
headless: true,
sessionMode: 'isolated',
},
@@ -120,7 +132,9 @@ describe.skipIf(!chromeAvailable)('browser-agent', () => {
const toolLogs = rig.readToolLogs();
const browserCalls = toolLogs.filter(
(t) => t.toolRequest.name === 'browser_agent',
(t) =>
t.toolRequest.name === 'invoke_agent' &&
JSON.parse(t.toolRequest.args).agent_name === 'browser_agent',
);
expect(browserCalls.length).toBeGreaterThan(0);
@@ -132,7 +146,12 @@ describe.skipIf(!chromeAvailable)('browser-agent', () => {
fakeResponsesPath: join(__dirname, 'browser-agent.interaction.responses'),
settings: {
agents: {
browser_agent: {
overrides: {
browser_agent: {
enabled: true,
},
},
browser: {
headless: true,
sessionMode: 'isolated',
},
@@ -146,7 +165,9 @@ describe.skipIf(!chromeAvailable)('browser-agent', () => {
const toolLogs = rig.readToolLogs();
const browserAgentCall = toolLogs.find(
(t) => t.toolRequest.name === 'browser_agent',
(t) =>
t.toolRequest.name === 'invoke_agent' &&
JSON.parse(t.toolRequest.args).agent_name === 'browser_agent',
);
expect(
browserAgentCall,
@@ -161,7 +182,12 @@ describe.skipIf(!chromeAvailable)('browser-agent', () => {
fakeResponsesPath: join(__dirname, 'browser-agent.cleanup.responses'),
settings: {
agents: {
browser_agent: {
overrides: {
browser_agent: {
enabled: true,
},
},
browser: {
headless: true,
sessionMode: 'isolated',
},
@@ -182,7 +208,12 @@ describe.skipIf(!chromeAvailable)('browser-agent', () => {
fakeResponsesPath: join(__dirname, 'browser-agent.sequential.responses'),
settings: {
agents: {
browser_agent: {
overrides: {
browser_agent: {
enabled: true,
},
},
browser: {
headless: true,
sessionMode: 'isolated',
},
@@ -196,7 +227,9 @@ describe.skipIf(!chromeAvailable)('browser-agent', () => {
const toolLogs = rig.readToolLogs();
const browserCalls = toolLogs.filter(
(t) => t.toolRequest.name === 'browser_agent',
(t) =>
t.toolRequest.name === 'invoke_agent' &&
JSON.parse(t.toolRequest.args).agent_name === 'browser_agent',
);
expect(browserCalls.length).toBeGreaterThan(0);
@@ -204,6 +237,54 @@ describe.skipIf(!chromeAvailable)('browser-agent', () => {
assertModelHasOutput(result);
});
it('should keep browser open across multiple browser_agent invocations', async () => {
rig.setup('browser-persistent-session', {
fakeResponsesPath: join(
__dirname,
'browser-agent.persistent-session.responses',
),
settings: {
agents: {
overrides: {
browser_agent: {
enabled: true,
},
},
browser: {
headless: true,
sessionMode: 'isolated',
allowedDomains: ['example.com'],
},
},
},
});
const result = await rig.run({
args: 'First, ask the browser agent to get the page title of example.com. After you receive that response, you MUST invoke the browser agent a second time to check for links on the page.',
});
const toolLogs = rig.readToolLogs();
const browserCalls = toolLogs.filter(
(t) =>
t.toolRequest.name === 'invoke_agent' &&
JSON.parse(t.toolRequest.args).agent_name === 'browser_agent',
);
// Both browser_agent invocations must succeed — if the browser was
// incorrectly closed after the first call (regression #24210),
// the second call would fail.
expect(
browserCalls.length,
'Expected browser_agent to be called twice',
).toBe(2);
expect(
browserCalls.every((c) => c.toolRequest.success),
'Both browser_agent calls should succeed',
).toBe(true);
assertModelHasOutput(result);
});
it('should handle tool confirmation for write_file without crashing', async () => {
rig.setup('tool-confirmation', {
fakeResponsesPath: join(
@@ -212,7 +293,12 @@ describe.skipIf(!chromeAvailable)('browser-agent', () => {
),
settings: {
agents: {
browser_agent: {
overrides: {
browser_agent: {
enabled: true,
},
},
browser: {
headless: true,
sessionMode: 'isolated',
},
@@ -232,4 +318,50 @@ describe.skipIf(!chromeAvailable)('browser-agent', () => {
await run.expectText('successfully written', 15000);
});
it('should handle concurrent browser agents with isolated session mode', async () => {
rig.setup('browser-concurrent', {
fakeResponsesPath: join(__dirname, 'browser-agent.concurrent.responses'),
settings: {
agents: {
overrides: {
browser_agent: {
enabled: true,
},
},
browser: {
headless: true,
// Isolated mode supports concurrent browser agents.
// Persistent/existing modes reject concurrent calls to prevent
// Chrome profile lock conflicts.
sessionMode: 'isolated',
},
},
},
});
const result = await rig.run({
args: 'Launch two browser agents concurrently to check example.com',
});
assertModelHasOutput(result);
const toolLogs = rig.readToolLogs();
const browserCalls = toolLogs.filter(
(t) =>
t.toolRequest.name === 'invoke_agent' &&
JSON.parse(t.toolRequest.args).agent_name === 'browser_agent',
);
// Both browser_agent invocations should have been called
expect(browserCalls.length).toBe(2);
// Both should complete successfully (no errors)
for (const call of browserCalls) {
expect(
call.toolRequest.success,
`browser_agent call failed: ${JSON.stringify(call.toolRequest)}`,
).toBe(true);
}
});
});
+1 -1
View File
@@ -1,4 +1,4 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I'll help you with that."},{"functionCall":{"name":"browser_agent","args":{"task":"Open https://example.com and check if there is a heading"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":50,"totalTokenCount":150}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I'll help you with that."},{"functionCall":{"name":"invoke_agent","args":{"agent_name":"browser_agent","prompt":"Open https://example.com and check if there is a heading"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":50,"totalTokenCount":150}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"new_page","args":{"url":"https://example.com"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":50,"totalTokenCount":150}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_snapshot","args":{}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":50,"totalTokenCount":150}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"complete_task","args":{"success":true,"summary":"SUCCESS_POLICY_TEST_COMPLETED"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":50,"totalTokenCount":150}}]}
+2 -1
View File
@@ -112,7 +112,8 @@ describe.skipIf(!chromeAvailable)('browser-policy', () => {
`
[[rule]]
name = "Force confirm browser_agent"
toolName = "browser_agent"
toolName = "invoke_agent"
argsPattern = "\\"agent_name\\":\\\\s*\\"browser_agent\\""
decision = "ask_user"
priority = 200
`,
+81 -15
View File
@@ -9,30 +9,88 @@ if (process.env['NO_COLOR'] !== undefined) {
delete process.env['NO_COLOR'];
}
import { mkdir, readdir, rm } from 'node:fs/promises';
import { join, dirname } from 'node:path';
import { mkdir, readdir, rm, readFile } from 'node:fs/promises';
import { join, dirname, extname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { canUseRipgrep } from '../packages/core/src/tools/ripGrep.js';
import { disableMouseTracking } from '@google/gemini-cli-core';
import { isolateTestEnv } from '../packages/test-utils/src/env-setup.js';
import { createServer, type Server } from 'node:http';
const __dirname = dirname(fileURLToPath(import.meta.url));
const rootDir = join(__dirname, '..');
const integrationTestsDir = join(rootDir, '.integration-tests');
let runDir = ''; // Make runDir accessible in teardown
let fixtureServer: Server | undefined;
const FIXTURE_PORT = 18923;
const FIXTURE_DIR = join(__dirname, 'test-fixtures');
const MIME_TYPES: Record<string, string> = {
'.html': 'text/html',
'.css': 'text/css',
'.js': 'application/javascript',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.svg': 'image/svg+xml',
};
async function startFixtureServer(): Promise<number> {
return new Promise((resolve, reject) => {
const server = createServer(async (req, res) => {
const urlPath = req.url?.split('?')[0] || '/';
const relativePath = urlPath === '/' ? 'index.html' : urlPath;
const filePath = join(FIXTURE_DIR, relativePath);
if (!filePath.startsWith(FIXTURE_DIR)) {
res.writeHead(403, { 'Content-Type': 'text/html' });
res.end('<h1>403 Forbidden</h1>');
return;
}
try {
const content = await readFile(filePath);
const ext = extname(filePath);
res.writeHead(200, {
'Content-Type': MIME_TYPES[ext] || 'application/octet-stream',
});
res.end(content);
} catch {
res.writeHead(404, { 'Content-Type': 'text/html' });
res.end('<h1>404 Not Found</h1>');
}
});
server.on('error', (err: NodeJS.ErrnoException) => {
if (err.code === 'EADDRINUSE') {
console.warn(
`Port ${FIXTURE_PORT} in use, trying ${FIXTURE_PORT + 1}...`,
);
server.listen(FIXTURE_PORT + 1, '127.0.0.1');
} else {
reject(err);
}
});
server.on('listening', () => {
const addr = server.address();
const port = typeof addr === 'object' && addr ? addr.port : FIXTURE_PORT;
fixtureServer = server;
console.log(`Test fixture server listening on http://127.0.0.1:${port}`);
resolve(port);
});
server.listen(FIXTURE_PORT, '127.0.0.1');
});
}
export async function setup() {
runDir = join(integrationTestsDir, `${Date.now()}`);
await mkdir(runDir, { recursive: true });
// Set the home directory to the test run directory to avoid conflicts
// with the user's local config.
process.env['HOME'] = runDir;
if (process.platform === 'win32') {
process.env['USERPROFILE'] = runDir;
}
// We also need to set the config dir explicitly, since the code might
// construct the path before the HOME env var is set.
process.env['GEMINI_CONFIG_DIR'] = join(runDir, '.gemini');
// Isolate environment variables
isolateTestEnv(runDir);
// Download ripgrep to avoid race conditions in parallel tests
const available = await canUseRipgrep();
@@ -40,6 +98,10 @@ export async function setup() {
throw new Error('Failed to download ripgrep binary');
}
// Start the test fixture server
const port = await startFixtureServer();
process.env['TEST_FIXTURE_PORT'] = String(port);
// Clean up old test runs, but keep the latest few for debugging
try {
const testRuns = await readdir(integrationTestsDir);
@@ -59,10 +121,6 @@ export async function setup() {
}
process.env['INTEGRATION_TEST_FILE_DIR'] = runDir;
process.env['GEMINI_CLI_INTEGRATION_TEST'] = 'true';
// Force file storage to avoid keychain prompts/hangs in CI, especially on macOS
process.env['GEMINI_FORCE_FILE_STORAGE'] = 'true';
process.env['TELEMETRY_LOG_FILE'] = join(runDir, 'telemetry.log');
if (process.env['KEEP_OUTPUT']) {
console.log(`Keeping output for test run in: ${runDir}`);
@@ -73,6 +131,14 @@ export async function setup() {
}
export async function teardown() {
// Stop the fixture server
if (fixtureServer) {
await new Promise<void>((resolve) => {
fixtureServer!.close(() => resolve());
});
fixtureServer = undefined;
}
// Disable mouse tracking
if (process.stdout.isTTY) {
disableMouseTracking();
@@ -0,0 +1,5 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I will run the command in the background for you."},{"functionCall":{"name":"run_shell_command","args":{"command":"sleep 10 && echo hello-from-background","is_background":true}}}],"role":"model"},"finishReason":"STOP","index":0}]}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The background process has been started. Now I will list the background processes to verify."},{"functionCall":{"name":"list_background_processes","args":{}}}],"role":"model"},"finishReason":"STOP","index":0}]}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I see the background process 'sleep 10 && echo hello-from-background' is running. Would you like me to read its output?"}],"role":"model"},"finishReason":"STOP","index":0}]}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I will read the output for you."},{"functionCall":{"name":"read_background_output","args":{"pid":12345}}}],"role":"model"},"finishReason":"STOP","index":0}]}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The output of the background process is:\nhello-from-background"}],"role":"model"},"finishReason":"STOP","index":0}]}]}
+105
View File
@@ -0,0 +1,105 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, beforeEach, afterEach } from 'vitest';
import { TestRig } from './test-helper.js';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
describe('shell-background-tools', () => {
let rig: TestRig;
beforeEach(() => {
rig = new TestRig();
});
afterEach(async () => await rig.cleanup());
it('should run a command in the background, list it, and read its output', async () => {
// We use a fake responses file to make the test deterministic and run in CI.
rig.setup('shell-background-workflow', {
fakeResponsesPath: join(__dirname, 'shell-background.responses'),
settings: {
tools: {
core: [
'run_shell_command',
'list_background_processes',
'read_background_output',
],
},
hooksConfig: {
enabled: true,
},
hooks: {
BeforeTool: [
{
matcher: 'run_shell_command',
hooks: [
{
type: 'command',
// This hook intercepts run_shell_command.
// If is_background is true, it returns a mock result with PID 12345.
// It also creates the mock log file that read_background_output expects.
command: `node -e "
const fs = require('fs');
const path = require('path');
const input = JSON.parse(fs.readFileSync(0, 'utf-8'));
const args = JSON.parse(input.tool_call.args);
if (args.is_background) {
const logDir = path.join(process.env.GEMINI_CLI_HOME, 'background-processes');
if (!fs.existsSync(logDir)) fs.mkdirSync(logDir, { recursive: true });
fs.writeFileSync(path.join(logDir, 'background-12345.log'), 'hello-from-background\\n');
console.log(JSON.stringify({
decision: 'replace',
hookSpecificOutput: {
result: {
llmContent: 'Command moved to background (PID: 12345). Output hidden. Press Ctrl+B to view.',
data: { pid: 12345, command: args.command }
}
}
}));
} else {
console.log(JSON.stringify({ decision: 'allow' }));
}
"`,
},
],
},
],
},
},
});
const run = await rig.runInteractive({ approvalMode: 'yolo' });
// 1. Start a background process
// We use a command that stays alive for a bit to ensure it shows up in lists
await run.type(
"Run 'sleep 10 && echo hello-from-background' in the background.",
);
await run.type('\r');
// Wait for the model's canned response acknowledging the start
await run.expectText('background', 30000);
// 2. List background processes
await run.type('List my background processes.');
await run.type('\r');
// Wait for the model's canned response showing the list
await run.expectText('hello-from-background', 30000);
// 3. Read the output
await run.type('Read the output of that process.');
await run.type('\r');
// Wait for the model's canned response showing the output
await run.expectText('hello-from-background', 30000);
}, 60000);
});
@@ -0,0 +1,29 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Test Fixture - Dynamic Content</title>
</head>
<body>
<h1 id="dynamic-heading">Dynamic Content Page</h1>
<div id="loading-indicator">Loading...</div>
<div id="dynamic-content" style="display: none">
<h2 id="loaded-heading">Content Loaded</h2>
<p id="loaded-message">
This content was loaded dynamically via JavaScript.
</p>
<ul id="item-list">
<li>Item Alpha</li>
<li>Item Beta</li>
<li>Item Gamma</li>
</ul>
</div>
<script>
// Simulate async content loading
setTimeout(function () {
document.getElementById('loading-indicator').style.display = 'none';
document.getElementById('dynamic-content').style.display = 'block';
}, 500);
</script>
</body>
</html>
@@ -0,0 +1,15 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Test Fixture - Form Result</title>
</head>
<body>
<h1 id="result-heading">Form Submitted Successfully</h1>
<p id="result-message">Thank you for your submission.</p>
<div id="submitted-data">
<p>Your form data has been received.</p>
</div>
<a href="/index.html" id="home-link">Back to Home</a>
</body>
</html>
+37
View File
@@ -0,0 +1,37 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Test Fixture - Contact Form</title>
</head>
<body>
<h1 id="form-heading">Contact Form</h1>
<form id="contact-form" action="/form-result.html" method="GET">
<div>
<label for="name">Name:</label>
<input type="text" id="name" name="name" required />
</div>
<div>
<label for="email">Email:</label>
<input type="email" id="email" name="email" required />
</div>
<div>
<label for="message">Message:</label>
<textarea id="message" name="message" rows="4"></textarea>
</div>
<div>
<label for="category">Category:</label>
<select id="category" name="category">
<option value="general">General</option>
<option value="support">Support</option>
<option value="feedback">Feedback</option>
</select>
</div>
<div>
<input type="checkbox" id="subscribe" name="subscribe" value="yes" />
<label for="subscribe">Subscribe to newsletter</label>
</div>
<button type="submit" id="submit-btn">Submit</button>
</form>
</body>
</html>
@@ -0,0 +1,27 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Test Fixture - Home</title>
</head>
<body>
<h1 id="main-heading">Test Fixture Home Page</h1>
<p id="description">
This is a test fixture page for browser agent integration tests.
</p>
<nav>
<ul>
<li><a href="/form.html" id="form-link">Contact Form</a></li>
<li>
<a href="/multi-step/step1.html" id="multistep-link"
>Multi-Step Flow</a
>
</li>
<li><a href="/dynamic.html" id="dynamic-link">Dynamic Content</a></li>
</ul>
</nav>
<footer id="footer">
<p>Footer content for testing.</p>
</footer>
</body>
</html>
@@ -0,0 +1,15 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Test Fixture - Result</title>
</head>
<body>
<h1 id="result-heading">Multi-Step Complete</h1>
<p id="result-message">You have completed all steps successfully.</p>
<div id="completion-status">
<span id="status-badge">✓ Complete</span>
</div>
<a href="/index.html" id="home-link">Back to Home</a>
</body>
</html>
@@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Test Fixture - Step 1</title>
</head>
<body>
<h1 id="step-heading">Step 1: Enter Your Details</h1>
<p id="step-description">Please provide your name to continue.</p>
<form id="step1-form" action="/multi-step/step2.html" method="GET">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required />
<button type="submit" id="next-btn">Next Step</button>
</form>
</body>
</html>
@@ -0,0 +1,22 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Test Fixture - Step 2</title>
</head>
<body>
<h1 id="step-heading">Step 2: Confirm Your Selection</h1>
<p id="step-description">Choose your preference below.</p>
<form id="step2-form" action="/multi-step/result.html" method="GET">
<div>
<label for="preference">Preference:</label>
<select id="preference" name="preference">
<option value="option-a">Option A</option>
<option value="option-b">Option B</option>
<option value="option-c">Option C</option>
</select>
</div>
<button type="submit" id="finish-btn">Finish</button>
</form>
</body>
</html>
+2 -19
View File
@@ -5,8 +5,8 @@
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { writeFileSync, readFileSync } from 'node:fs';
import { join, resolve } from 'node:path';
import { writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { TestRig } from './test-helper.js';
// BOM encoders
@@ -116,21 +116,4 @@ describe('BOM end-to-end integraion', () => {
'BOM_OK UTF-32BE',
);
});
it('Can describe a PNG file', async () => {
const imagePath = resolve(
process.cwd(),
'docs/assets/gemini-screenshot.png',
);
const imageContent = readFileSync(imagePath);
const filename = 'gemini-screenshot.png';
writeFileSync(join(rig.testDir!, filename), imageContent);
const prompt = `What is shown in the image ${filename}?`;
const output = await rig.run({ args: prompt });
await rig.waitForToolCall('read_file');
const lower = output.toLowerCase();
// The response is non-deterministic, so we just check for some
// keywords that are very likely to be in the response.
expect(lower.includes('gemini')).toBeTruthy();
});
});
+30
View File
@@ -0,0 +1,30 @@
{
"version": 1,
"updatedAt": "2026-04-08T01:21:58.770Z",
"scenarios": {
"multi-turn-conversation": {
"heapUsedBytes": 120082704,
"heapTotalBytes": 177586176,
"rssBytes": 269172736,
"timestamp": "2026-04-08T01:21:57.127Z"
},
"multi-function-call-repo-search": {
"heapUsedBytes": 104644984,
"heapTotalBytes": 111575040,
"rssBytes": 204079104,
"timestamp": "2026-04-08T01:21:58.770Z"
},
"idle-session-startup": {
"heapUsedBytes": 119813672,
"heapTotalBytes": 177061888,
"rssBytes": 267943936,
"timestamp": "2026-04-08T01:21:53.855Z"
},
"simple-prompt-response": {
"heapUsedBytes": 119722064,
"heapTotalBytes": 177324032,
"rssBytes": 268812288,
"timestamp": "2026-04-08T01:21:55.491Z"
}
}
}
+71
View File
@@ -0,0 +1,71 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { mkdir, readdir, rm } from 'node:fs/promises';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { canUseRipgrep } from '../packages/core/src/tools/ripGrep.js';
const __dirname = dirname(fileURLToPath(import.meta.url));
const rootDir = join(__dirname, '..');
const memoryTestsDir = join(rootDir, '.memory-tests');
let runDir = '';
export async function setup() {
runDir = join(memoryTestsDir, `${Date.now()}`);
await mkdir(runDir, { recursive: true });
// Set the home directory to the test run directory to avoid conflicts
// with the user's local config.
process.env['HOME'] = runDir;
if (process.platform === 'win32') {
process.env['USERPROFILE'] = runDir;
}
process.env['GEMINI_CONFIG_DIR'] = join(runDir, '.gemini');
// Download ripgrep to avoid race conditions
const available = await canUseRipgrep();
if (!available) {
throw new Error('Failed to download ripgrep binary');
}
// Clean up old test runs, keeping the latest few for debugging
try {
const testRuns = await readdir(memoryTestsDir);
if (testRuns.length > 3) {
const oldRuns = testRuns.sort().slice(0, testRuns.length - 3);
await Promise.all(
oldRuns.map((oldRun) =>
rm(join(memoryTestsDir, oldRun), {
recursive: true,
force: true,
}),
),
);
}
} catch (e) {
console.error('Error cleaning up old memory test runs:', e);
}
process.env['INTEGRATION_TEST_FILE_DIR'] = runDir;
process.env['GEMINI_CLI_INTEGRATION_TEST'] = 'true';
process.env['GEMINI_FORCE_FILE_STORAGE'] = 'true';
process.env['TELEMETRY_LOG_FILE'] = join(runDir, 'telemetry.log');
process.env['VERBOSE'] = process.env['VERBOSE'] ?? 'false';
console.log(`\nMemory test output directory: ${runDir}`);
}
export async function teardown() {
// Cleanup unless KEEP_OUTPUT is set
if (process.env['KEEP_OUTPUT'] !== 'true' && runDir) {
try {
await rm(runDir, { recursive: true, force: true });
} catch (e) {
console.warn('Failed to clean up memory test directory:', e);
}
}
}
+185
View File
@@ -0,0 +1,185 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, beforeAll, afterAll, afterEach } from 'vitest';
import { TestRig, MemoryTestHarness } from '@google/gemini-cli-test-utils';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const BASELINES_PATH = join(__dirname, 'baselines.json');
const UPDATE_BASELINES = process.env['UPDATE_MEMORY_BASELINES'] === 'true';
const TOLERANCE_PERCENT = 10;
// Fake API key for tests using fake responses
const TEST_ENV = { GEMINI_API_KEY: 'fake-memory-test-key' };
describe('Memory Usage Tests', () => {
let harness: MemoryTestHarness;
let rig: TestRig;
beforeAll(() => {
harness = new MemoryTestHarness({
baselinesPath: BASELINES_PATH,
defaultTolerancePercent: TOLERANCE_PERCENT,
gcCycles: 3,
gcDelayMs: 100,
sampleCount: 3,
});
});
afterEach(async () => {
await rig.cleanup();
});
afterAll(async () => {
// Generate the summary report after all tests
await harness.generateReport();
});
it('idle-session-startup: memory usage within baseline', async () => {
rig = new TestRig();
rig.setup('memory-idle-startup', {
fakeResponsesPath: join(__dirname, 'memory.idle-startup.responses'),
});
const result = await harness.runScenario(
'idle-session-startup',
async (recordSnapshot) => {
await rig.run({
args: ['hello'],
timeout: 120000,
env: TEST_ENV,
});
await recordSnapshot('after-startup');
},
);
if (UPDATE_BASELINES) {
harness.updateScenarioBaseline(result);
console.log(
`Updated baseline for idle-session-startup: ${(result.finalHeapUsed / (1024 * 1024)).toFixed(1)} MB`,
);
} else {
harness.assertWithinBaseline(result);
}
});
it('simple-prompt-response: memory usage within baseline', async () => {
rig = new TestRig();
rig.setup('memory-simple-prompt', {
fakeResponsesPath: join(__dirname, 'memory.simple-prompt.responses'),
});
const result = await harness.runScenario(
'simple-prompt-response',
async (recordSnapshot) => {
await rig.run({
args: ['What is the capital of France?'],
timeout: 120000,
env: TEST_ENV,
});
await recordSnapshot('after-response');
},
);
if (UPDATE_BASELINES) {
harness.updateScenarioBaseline(result);
console.log(
`Updated baseline for simple-prompt-response: ${(result.finalHeapUsed / (1024 * 1024)).toFixed(1)} MB`,
);
} else {
harness.assertWithinBaseline(result);
}
});
it('multi-turn-conversation: memory remains stable over turns', async () => {
rig = new TestRig();
rig.setup('memory-multi-turn', {
fakeResponsesPath: join(__dirname, 'memory.multi-turn.responses'),
});
const prompts = [
'Hello, what can you help me with?',
'Tell me about JavaScript',
'How is TypeScript different?',
'Can you write a simple TypeScript function?',
'What are some TypeScript best practices?',
];
const result = await harness.runScenario(
'multi-turn-conversation',
async (recordSnapshot) => {
// Run through all turns as a piped sequence
const stdinContent = prompts.join('\n');
await rig.run({
stdin: stdinContent,
timeout: 120000,
env: TEST_ENV,
});
// Take snapshots after the conversation completes
await recordSnapshot('after-all-turns');
},
);
if (UPDATE_BASELINES) {
harness.updateScenarioBaseline(result);
console.log(
`Updated baseline for multi-turn-conversation: ${(result.finalHeapUsed / (1024 * 1024)).toFixed(1)} MB`,
);
} else {
harness.assertWithinBaseline(result);
}
});
it('multi-function-call-repo-search: memory after tool use', async () => {
rig = new TestRig();
rig.setup('memory-multi-func-call', {
fakeResponsesPath: join(
__dirname,
'memory.multi-function-call.responses',
),
});
// Create directories first, then files in the workspace so the tools have targets
rig.mkdir('packages/core/src/telemetry');
rig.createFile(
'packages/core/src/telemetry/memory-monitor.ts',
'export class MemoryMonitor { constructor() {} }',
);
rig.createFile(
'packages/core/src/telemetry/metrics.ts',
'export function recordMemoryUsage() {}',
);
const result = await harness.runScenario(
'multi-function-call-repo-search',
async (recordSnapshot) => {
await rig.run({
args: [
'Search this repository for MemoryMonitor and tell me what it does',
],
timeout: 120000,
env: TEST_ENV,
});
await recordSnapshot('after-tool-calls');
},
);
if (UPDATE_BASELINES) {
harness.updateScenarioBaseline(result);
console.log(
`Updated baseline for multi-function-call-repo-search: ${(result.finalHeapUsed / (1024 * 1024)).toFixed(1)} MB`,
);
} else {
harness.assertWithinBaseline(result);
}
});
});
@@ -0,0 +1,2 @@
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"0"}],"role":"model"},"finishReason":"STOP","index":0}]}}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Hello! I'm ready to help. What would you like to work on?"}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":5,"candidatesTokenCount":12,"totalTokenCount":17,"promptTokensDetails":[{"modality":"TEXT","tokenCount":5}]}}]}
@@ -0,0 +1,4 @@
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"0"}],"role":"model"},"finishReason":"STOP","index":0}]}}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I'll search for MemoryMonitor in the repository and analyze what it does."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":30,"candidatesTokenCount":15,"totalTokenCount":45,"promptTokensDetails":[{"modality":"TEXT","tokenCount":30}]}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"grep_search","args":{"pattern":"MemoryMonitor","path":".","include_pattern":"*.ts"}}},{"functionCall":{"name":"list_directory","args":{"path":"packages/core/src/telemetry"}}},{"functionCall":{"name":"read_file","args":{"file_path":"packages/core/src/telemetry/memory-monitor.ts"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":30,"candidatesTokenCount":80,"totalTokenCount":110,"promptTokensDetails":[{"modality":"TEXT","tokenCount":30}]}}]}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I found the memory monitoring code. Here's a summary:\n\nThe `MemoryMonitor` class in `packages/core/src/telemetry/memory-monitor.ts` provides:\n\n1. **Continuous monitoring** via `start()`/`stop()` with configurable intervals\n2. **V8 heap snapshots** using `v8.getHeapStatistics()` and `process.memoryUsage()`\n3. **High-water mark tracking** to detect significant memory growth\n4. **Rate-limited recording** to avoid metric flood\n5. **Activity detection** — only records when user is active\n\nThe class uses a singleton pattern via `initializeMemoryMonitor()` for global access."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":500,"candidatesTokenCount":120,"totalTokenCount":620,"promptTokensDetails":[{"modality":"TEXT","tokenCount":500}]}}]}
+10
View File
@@ -0,0 +1,10 @@
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"0"}],"role":"model"},"finishReason":"STOP","index":0}]}}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Hello! I'm ready to help you with your coding tasks. What would you like to work on today?"}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":5,"candidatesTokenCount":18,"totalTokenCount":23,"promptTokensDetails":[{"modality":"TEXT","tokenCount":5}]}}]}
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"0"}],"role":"model"},"finishReason":"STOP","index":0}]}}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"JavaScript is a high-level, interpreted programming language. It was originally designed for adding interactivity to web pages."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":25,"candidatesTokenCount":60,"totalTokenCount":85,"promptTokensDetails":[{"modality":"TEXT","tokenCount":25}]}}]}
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"0"}],"role":"model"},"finishReason":"STOP","index":0}]}}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"TypeScript is a typed superset of JavaScript developed by Microsoft. The main differences from JavaScript are static typing and better tooling."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":45,"candidatesTokenCount":80,"totalTokenCount":125,"promptTokensDetails":[{"modality":"TEXT","tokenCount":45}]}}]}
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"0"}],"role":"model"},"finishReason":"STOP","index":0}]}}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Here is a simple TypeScript function:\n\nfunction greet(name: string): string { return `Hello, ${name}!`; }"}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":60,"candidatesTokenCount":55,"totalTokenCount":115,"promptTokensDetails":[{"modality":"TEXT","tokenCount":60}]}}]}
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"0"}],"role":"model"},"finishReason":"STOP","index":0}]}}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Here are 5 key TypeScript best practices: Enable strict mode, prefer interfaces, use union types, leverage type inference, and use readonly."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":75,"candidatesTokenCount":70,"totalTokenCount":145,"promptTokensDetails":[{"modality":"TEXT","tokenCount":75}]}}]}
@@ -0,0 +1,2 @@
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"0"}],"role":"model"},"finishReason":"STOP","index":0}]}}
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The capital of France is Paris. It has been the capital since the 10th century and is known for iconic landmarks like the Eiffel Tower, the Louvre Museum, and Notre-Dame Cathedral. Paris is also the most populous city in France, with a metropolitan area population of over 12 million people."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":7,"candidatesTokenCount":55,"totalTokenCount":62,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7}]}}]}
+12
View File
@@ -0,0 +1,12 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"noEmit": true,
"allowJs": true
},
"include": ["**/*.ts"],
"references": [
{ "path": "../packages/core" },
{ "path": "../packages/test-utils" }
]
}
+28
View File
@@ -0,0 +1,28 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
testTimeout: 600000, // 10 minutes — memory profiling is slow
globalSetup: './globalSetup.ts',
reporters: ['default'],
include: ['**/*.test.ts'],
retry: 0, // No retries for memory tests — noise is handled by tolerance
fileParallelism: false, // Must run serially to avoid memory interference
pool: 'forks', // Use forks pool for --expose-gc support
poolOptions: {
forks: {
singleFork: true, // Single process for accurate per-test memory readings
execArgv: ['--expose-gc'], // Enable global.gc() for forced GC
},
},
env: {
GEMINI_TEST_TYPE: 'memory',
},
},
});
+22 -14
View File
@@ -1,17 +1,17 @@
{
"name": "@google/gemini-cli",
"version": "0.36.0-nightly.20260317.2f90b4653",
"version": "0.39.0-nightly.20260408.e77b22e63",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@google/gemini-cli",
"version": "0.36.0-nightly.20260317.2f90b4653",
"version": "0.39.0-nightly.20260408.e77b22e63",
"workspaces": [
"packages/*"
],
"dependencies": {
"ink": "npm:@jrichman/ink@6.6.3",
"ink": "npm:@jrichman/ink@6.6.9",
"latest-version": "^9.0.0",
"node-fetch-native": "^1.6.7",
"proper-lockfile": "^4.1.2",
@@ -36,6 +36,7 @@
"@types/ws": "^8.18.1",
"@vitest/coverage-v8": "^3.1.1",
"@vitest/eslint-plugin": "^1.3.4",
"asciichart": "^1.5.25",
"cross-env": "^7.0.3",
"depcheck": "^1.4.7",
"domexception": "^4.0.0",
@@ -5569,6 +5570,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/asciichart": {
"version": "1.5.25",
"resolved": "https://registry.npmjs.org/asciichart/-/asciichart-1.5.25.tgz",
"integrity": "sha512-PNxzXIPPOtWq8T7bgzBtk9cI2lgS4SJZthUHEiQ1aoIc3lNzGfUvIvo9LiAnq26TACo9t1/4qP6KTGAUbzX9Xg==",
"license": "MIT"
},
"node_modules/assertion-error": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
@@ -10049,9 +10056,9 @@
},
"node_modules/ink": {
"name": "@jrichman/ink",
"version": "6.6.3",
"resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.6.3.tgz",
"integrity": "sha512-0v4S7TbbF2tpQrfqH1btwLgTgH+K0vY2BJbokTE5Lk1KBr4TqZ+Pyo+geSD5F+zytX6G2ajGHBQyHk8yGK4C7A==",
"version": "6.6.9",
"resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.6.9.tgz",
"integrity": "sha512-RL9sSiLQZECnjbmBwjIHOp8yVGdWF7C/uifg7ISv/e+F3nLNsfl7FdUFQs8iZARFMJAYxMFpxW6OW+HSt9drwQ==",
"license": "MIT",
"dependencies": {
"ansi-escapes": "^7.0.0",
@@ -17389,7 +17396,7 @@
},
"packages/a2a-server": {
"name": "@google/gemini-cli-a2a-server",
"version": "0.36.0-nightly.20260317.2f90b4653",
"version": "0.39.0-nightly.20260408.e77b22e63",
"dependencies": {
"@a2a-js/sdk": "0.3.11",
"@google-cloud/storage": "^7.16.0",
@@ -17504,7 +17511,7 @@
},
"packages/cli": {
"name": "@google/gemini-cli",
"version": "0.36.0-nightly.20260317.2f90b4653",
"version": "0.39.0-nightly.20260408.e77b22e63",
"license": "Apache-2.0",
"dependencies": {
"@agentclientprotocol/sdk": "^0.16.1",
@@ -17526,7 +17533,7 @@
"fzf": "^0.5.2",
"glob": "^12.0.0",
"highlight.js": "^11.11.1",
"ink": "npm:@jrichman/ink@6.6.3",
"ink": "npm:@jrichman/ink@6.6.9",
"ink-gradient": "^3.0.0",
"ink-spinner": "^5.0.0",
"latest-version": "^9.0.0",
@@ -17676,7 +17683,7 @@
},
"packages/core": {
"name": "@google/gemini-cli-core",
"version": "0.36.0-nightly.20260317.2f90b4653",
"version": "0.39.0-nightly.20260408.e77b22e63",
"license": "Apache-2.0",
"dependencies": {
"@a2a-js/sdk": "0.3.11",
@@ -17942,7 +17949,7 @@
},
"packages/devtools": {
"name": "@google/gemini-cli-devtools",
"version": "0.36.0-nightly.20260317.2f90b4653",
"version": "0.39.0-nightly.20260408.e77b22e63",
"license": "Apache-2.0",
"dependencies": {
"ws": "^8.16.0"
@@ -17957,7 +17964,7 @@
},
"packages/sdk": {
"name": "@google/gemini-cli-sdk",
"version": "0.36.0-nightly.20260317.2f90b4653",
"version": "0.39.0-nightly.20260408.e77b22e63",
"license": "Apache-2.0",
"dependencies": {
"@google/gemini-cli-core": "file:../core",
@@ -17974,11 +17981,12 @@
},
"packages/test-utils": {
"name": "@google/gemini-cli-test-utils",
"version": "0.36.0-nightly.20260317.2f90b4653",
"version": "0.39.0-nightly.20260408.e77b22e63",
"license": "Apache-2.0",
"dependencies": {
"@google/gemini-cli-core": "file:../core",
"@lydell/node-pty": "1.1.0",
"asciichart": "^1.5.25",
"strip-ansi": "^7.1.2",
"vitest": "^3.2.4"
},
@@ -17991,7 +17999,7 @@
},
"packages/vscode-ide-companion": {
"name": "gemini-cli-vscode-ide-companion",
"version": "0.36.0-nightly.20260317.2f90b4653",
"version": "0.39.0-nightly.20260408.e77b22e63",
"license": "LICENSE",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.23.0",

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