mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-24 00:30:59 -07:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| edd77168f5 | |||
| 8b133b9c0d | |||
| 5ec55fc718 | |||
| f4e7d4159a | |||
| 5b08f0644d | |||
| 9a6a049ed8 |
@@ -1,76 +0,0 @@
|
||||
---
|
||||
name: github-issue-creator
|
||||
description:
|
||||
Use this skill when asked to create a GitHub issue. It handles different issue
|
||||
types (bug, feature, etc.) using repository templates and ensures proper
|
||||
labeling.
|
||||
---
|
||||
|
||||
# GitHub Issue Creator
|
||||
|
||||
This skill guides the creation of high-quality GitHub issues that adhere to the
|
||||
repository's standards and use the appropriate templates.
|
||||
|
||||
## Workflow
|
||||
|
||||
Follow these steps to create a GitHub issue:
|
||||
|
||||
1. **Identify Issue Type**: Determine if the request is a bug report, feature
|
||||
request, or other category.
|
||||
|
||||
2. **Locate Template**: Search for issue templates in
|
||||
`.github/ISSUE_TEMPLATE/`.
|
||||
- `bug_report.yml`
|
||||
- `feature_request.yml`
|
||||
- `website_issue.yml`
|
||||
- If no relevant YAML template is found, look for `.md` templates in the same
|
||||
directory.
|
||||
|
||||
3. **Read Template**: Read the content of the identified template file to
|
||||
understand the required fields.
|
||||
|
||||
4. **Draft Content**: Draft the issue title and body/fields.
|
||||
- If using a YAML template (form), prepare values for each `id` defined in
|
||||
the template.
|
||||
- If using a Markdown template, follow its structure exactly.
|
||||
- **Default Label**: Always include the `🔒 maintainer only` label unless the
|
||||
user explicitly requests otherwise.
|
||||
|
||||
5. **Create Issue**: Use the `gh` CLI to create the issue.
|
||||
- **CRITICAL:** To avoid shell escaping and formatting issues with
|
||||
multi-line Markdown or complex text, ALWAYS write the description/body to
|
||||
a temporary file first.
|
||||
|
||||
**For Markdown Templates or Simple Body:**
|
||||
```bash
|
||||
# 1. Write the drafted content to a temporary file
|
||||
# 2. Create the issue using the --body-file flag
|
||||
gh issue create --title "Succinct title" --body-file <temp_file_path> --label "🔒 maintainer only"
|
||||
# 3. Remove the temporary file
|
||||
rm <temp_file_path>
|
||||
```
|
||||
|
||||
**For YAML Templates (Forms):**
|
||||
While `gh issue create` supports `--body-file`, YAML forms usually expect
|
||||
key-value pairs via flags if you want to bypass the interactive prompt.
|
||||
However, the most reliable non-interactive way to ensure formatting is
|
||||
preserved for long text fields is to use the `--body` or `--body-file` if the
|
||||
form has been converted to a standard body, OR to use the `--field` flags
|
||||
for YAML forms.
|
||||
|
||||
*Note: For the `gemini-cli` repository which uses YAML forms, you can often
|
||||
submit the content as a single body if a specific field-based submission is
|
||||
not required by the automation.*
|
||||
|
||||
6. **Verify**: Confirm the issue was created successfully and provide the link
|
||||
to the user.
|
||||
|
||||
## Principles
|
||||
|
||||
- **Clarity**: Titles should be descriptive and follow project conventions.
|
||||
- **Defensive Formatting**: Always use temporary files with `--body-file` to
|
||||
prevent newline and special character issues.
|
||||
- **Maintainer Priority**: Default to internal/maintainer labels to keep the
|
||||
backlog organized.
|
||||
- **Completeness**: Provide all requested information (e.g., version info,
|
||||
reproduction steps).
|
||||
@@ -145,7 +145,7 @@ jobs:
|
||||
branch-name: 'release/${{ steps.nightly_version.outputs.RELEASE_TAG }}'
|
||||
pr-title: 'chore/release: bump version to ${{ steps.nightly_version.outputs.RELEASE_VERSION }}'
|
||||
pr-body: 'Automated version bump for nightly release.'
|
||||
github-token: '${{ secrets.GEMINI_CLI_ROBOT_GITHUB_PAT }}'
|
||||
github-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
dry-run: '${{ steps.vars.outputs.is_dry_run }}'
|
||||
working-directory: './release'
|
||||
|
||||
|
||||
@@ -335,7 +335,6 @@ jobs:
|
||||
name: 'Create Nightly PR'
|
||||
needs: ['publish-stable', 'calculate-versions']
|
||||
runs-on: 'ubuntu-latest'
|
||||
environment: "${{ github.event.inputs.environment || 'prod' }}"
|
||||
permissions:
|
||||
contents: 'write'
|
||||
pull-requests: 'write'
|
||||
@@ -398,7 +397,7 @@ jobs:
|
||||
branch-name: '${{ steps.release_branch.outputs.BRANCH_NAME }}'
|
||||
pr-title: 'chore(release): bump version to ${{ needs.calculate-versions.outputs.NEXT_NIGHTLY_VERSION }}'
|
||||
pr-body: 'Automated version bump to prepare for the next nightly release.'
|
||||
github-token: '${{ secrets.GEMINI_CLI_ROBOT_GITHUB_PAT }}'
|
||||
github-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
dry-run: '${{ github.event.inputs.dry_run }}'
|
||||
|
||||
- name: 'Create Issue on Failure'
|
||||
|
||||
@@ -18,27 +18,6 @@ on GitHub.
|
||||
| [Preview](preview.md) | Experimental features ready for early feedback. |
|
||||
| [Stable](latest.md) | Stable, recommended for general use. |
|
||||
|
||||
## Announcements: v0.31.0 - 2026-02-27
|
||||
|
||||
- **Gemini 3.1 Pro Preview:** Gemini CLI now supports the new Gemini 3.1 Pro
|
||||
Preview model
|
||||
([#19676](https://github.com/google-gemini/gemini-cli/pull/19676) by
|
||||
@sehoon38).
|
||||
- **Experimental Browser Agent:** We've introduced a new experimental browser
|
||||
agent to interact with web pages
|
||||
([#19284](https://github.com/google-gemini/gemini-cli/pull/19284) by
|
||||
@gsquared94).
|
||||
- **Policy Engine Updates:** The policy engine now supports project-level
|
||||
policies, MCP server wildcards, and tool annotation matching
|
||||
([#18682](https://github.com/google-gemini/gemini-cli/pull/18682) by
|
||||
@Abhijit-2592,
|
||||
[#20024](https://github.com/google-gemini/gemini-cli/pull/20024) by @jerop).
|
||||
- **Web Fetch Improvements:** We've implemented an experimental direct web fetch
|
||||
feature and added rate limiting to mitigate DDoS risks
|
||||
([#19557](https://github.com/google-gemini/gemini-cli/pull/19557) by @mbleigh,
|
||||
[#19567](https://github.com/google-gemini/gemini-cli/pull/19567) by
|
||||
@mattKorwel).
|
||||
|
||||
## Announcements: v0.30.0 - 2026-02-25
|
||||
|
||||
- **SDK & Custom Skills:** Introduced the initial SDK package, enabling dynamic
|
||||
|
||||
+312
-391
@@ -1,4 +1,4 @@
|
||||
# Latest stable release: v0.31.0
|
||||
# Latest stable release: v0.30.1
|
||||
|
||||
Released: February 27, 2026
|
||||
|
||||
@@ -11,405 +11,326 @@ npm install -g @google/gemini-cli
|
||||
|
||||
## Highlights
|
||||
|
||||
- **Gemini 3.1 Pro Preview:** Gemini CLI now supports the new Gemini 3.1 Pro
|
||||
Preview model.
|
||||
- **Experimental Browser Agent:** We've introduced a new experimental browser
|
||||
agent to directly interact with web pages and retrieve context.
|
||||
- **Policy Engine Updates:** The policy engine has been expanded to support
|
||||
project-level policies, MCP server wildcards, and tool annotation matching,
|
||||
providing greater control over tool executions.
|
||||
- **Web Fetch Enhancements:** A new experimental direct web fetch tool has been
|
||||
implemented, alongside rate-limiting features for enhanced security.
|
||||
- **Improved Plan Mode:** Plan Mode now includes support for custom storage
|
||||
directories, automatic model switching, and summarizing work after execution.
|
||||
- **SDK & Custom Skills**: Introduced the initial SDK package, dynamic system
|
||||
instructions, `SessionContext` for SDK tool calls, and support for custom
|
||||
skills.
|
||||
- **Policy Engine Enhancements**: Added a `--policy` flag for user-defined
|
||||
policies, strict seatbelt profiles, and transitioned away from
|
||||
`--allowed-tools`.
|
||||
- **UI & Themes**: Introduced a generic searchable list for settings and
|
||||
extensions, added Solarized Dark and Light themes, text wrapping capabilities
|
||||
to markdown tables, and a clean UI toggle prototype.
|
||||
- **Vim Support & Ctrl-Z**: Improved Vim support to provide a more complete
|
||||
experience and added support for Ctrl-Z suspension.
|
||||
- **Plan Mode & Tools**: Plan Mode now supports project exploration without
|
||||
planning and skills can be enabled in plan mode. Tool output masking is
|
||||
enabled by default, and core tool definitions have been centralized.
|
||||
|
||||
## What's Changed
|
||||
|
||||
- Use ranged reads and limited searches and fuzzy editing improvements by
|
||||
@gundermanc in
|
||||
[#19240](https://github.com/google-gemini/gemini-cli/pull/19240)
|
||||
- Fix bottom border color by @jacob314 in
|
||||
[#19266](https://github.com/google-gemini/gemini-cli/pull/19266)
|
||||
- Release note generator fix by @g-samroberts in
|
||||
[#19363](https://github.com/google-gemini/gemini-cli/pull/19363)
|
||||
- test(evals): add behavioral tests for tool output masking by @NTaylorMullen in
|
||||
[#19172](https://github.com/google-gemini/gemini-cli/pull/19172)
|
||||
- docs: clarify preflight instructions in GEMINI.md by @NTaylorMullen in
|
||||
[#19377](https://github.com/google-gemini/gemini-cli/pull/19377)
|
||||
- feat(cli): add gemini --resume hint on exit by @Mag1ck in
|
||||
[#16285](https://github.com/google-gemini/gemini-cli/pull/16285)
|
||||
- fix: optimize height calculations for ask_user dialog by @jackwotherspoon in
|
||||
[#19017](https://github.com/google-gemini/gemini-cli/pull/19017)
|
||||
- feat(cli): add Alt+D for forward word deletion by @scidomino in
|
||||
[#19300](https://github.com/google-gemini/gemini-cli/pull/19300)
|
||||
- Disable failing eval test by @chrstnb in
|
||||
[#19455](https://github.com/google-gemini/gemini-cli/pull/19455)
|
||||
- fix(cli): support legacy onConfirm callback in ToolActionsContext by
|
||||
- fix(patch): cherry-pick 58df1c6 to release/v0.30.0-pr-20374 [CONFLICTS] by
|
||||
@gemini-cli-robot in
|
||||
[#20567](https://github.com/google-gemini/gemini-cli/pull/20567)
|
||||
- feat(ux): added text wrapping capabilities to markdown tables by @devr0306 in
|
||||
[#18240](https://github.com/google-gemini/gemini-cli/pull/18240)
|
||||
- Revert "fix(mcp): ensure MCP transport is closed to prevent memory leaks" by
|
||||
@skeshive in [#18771](https://github.com/google-gemini/gemini-cli/pull/18771)
|
||||
- chore(release): bump version to 0.30.0-nightly.20260210.a2174751d by
|
||||
@gemini-cli-robot in
|
||||
[#18772](https://github.com/google-gemini/gemini-cli/pull/18772)
|
||||
- chore: cleanup unused and add unlisted dependencies in packages/core by
|
||||
@adamfweidman in
|
||||
[#18762](https://github.com/google-gemini/gemini-cli/pull/18762)
|
||||
- chore(core): update activate_skill prompt verbiage to be more direct by
|
||||
@NTaylorMullen in
|
||||
[#18605](https://github.com/google-gemini/gemini-cli/pull/18605)
|
||||
- Add autoconfigure memory usage setting to the dialog by @jacob314 in
|
||||
[#18510](https://github.com/google-gemini/gemini-cli/pull/18510)
|
||||
- fix(core): prevent race condition in policy persistence by @braddux in
|
||||
[#18506](https://github.com/google-gemini/gemini-cli/pull/18506)
|
||||
- fix(evals): prevent false positive in hierarchical memory test by
|
||||
@Abhijit-2592 in
|
||||
[#18777](https://github.com/google-gemini/gemini-cli/pull/18777)
|
||||
- test(evals): mark all `save_memory` evals as `USUALLY_PASSES` due to
|
||||
unreliability by @jerop in
|
||||
[#18786](https://github.com/google-gemini/gemini-cli/pull/18786)
|
||||
- feat(cli): add setting to hide shortcuts hint UI by @LyalinDotCom in
|
||||
[#18562](https://github.com/google-gemini/gemini-cli/pull/18562)
|
||||
- feat(core): formalize 5-phase sequential planning workflow by @jerop in
|
||||
[#18759](https://github.com/google-gemini/gemini-cli/pull/18759)
|
||||
- Introduce limits for search results. by @gundermanc in
|
||||
[#18767](https://github.com/google-gemini/gemini-cli/pull/18767)
|
||||
- fix(cli): allow closing debug console after auto-open via flicker by
|
||||
@SandyTao520 in
|
||||
[#19369](https://github.com/google-gemini/gemini-cli/pull/19369)
|
||||
- chore(deps): bump tar from 7.5.7 to 7.5.8 by @.github/dependabot.yml[bot] in
|
||||
[#19367](https://github.com/google-gemini/gemini-cli/pull/19367)
|
||||
- fix(plan): allow safe fallback when experiment setting for plan is not enabled
|
||||
but approval mode at startup is plan by @Adib234 in
|
||||
[#19439](https://github.com/google-gemini/gemini-cli/pull/19439)
|
||||
- Add explicit color-convert dependency by @chrstnb in
|
||||
[#19460](https://github.com/google-gemini/gemini-cli/pull/19460)
|
||||
- feat(devtools): migrate devtools package into monorepo by @SandyTao520 in
|
||||
[#18936](https://github.com/google-gemini/gemini-cli/pull/18936)
|
||||
- fix(core): clarify plan mode constraints and exit mechanism by @jerop in
|
||||
[#19438](https://github.com/google-gemini/gemini-cli/pull/19438)
|
||||
- feat(cli): add macOS run-event notifications (interactive only) by
|
||||
[#18795](https://github.com/google-gemini/gemini-cli/pull/18795)
|
||||
- feat(masking): enable tool output masking by default by @abhipatel12 in
|
||||
[#18564](https://github.com/google-gemini/gemini-cli/pull/18564)
|
||||
- perf(ui): optimize table rendering by memoizing styled characters by @devr0306
|
||||
in [#18770](https://github.com/google-gemini/gemini-cli/pull/18770)
|
||||
- feat: multi-line text answers in ask-user tool by @jackwotherspoon in
|
||||
[#18741](https://github.com/google-gemini/gemini-cli/pull/18741)
|
||||
- perf(cli): truncate large debug logs and limit message history by @mattKorwel
|
||||
in [#18663](https://github.com/google-gemini/gemini-cli/pull/18663)
|
||||
- fix(core): complete MCP discovery when configured servers are skipped by
|
||||
@LyalinDotCom in
|
||||
[#19056](https://github.com/google-gemini/gemini-cli/pull/19056)
|
||||
- Changelog for v0.29.0 by @gemini-cli-robot in
|
||||
[#19361](https://github.com/google-gemini/gemini-cli/pull/19361)
|
||||
- fix(ui): preventing empty history items from being added by @devr0306 in
|
||||
[#19014](https://github.com/google-gemini/gemini-cli/pull/19014)
|
||||
- Changelog for v0.30.0-preview.0 by @gemini-cli-robot in
|
||||
[#19364](https://github.com/google-gemini/gemini-cli/pull/19364)
|
||||
- feat(core): add support for MCP progress updates by @NTaylorMullen in
|
||||
[#19046](https://github.com/google-gemini/gemini-cli/pull/19046)
|
||||
- fix(core): ensure directory exists before writing conversation file by
|
||||
@godwiniheuwa in
|
||||
[#18429](https://github.com/google-gemini/gemini-cli/pull/18429)
|
||||
- fix(ui): move margin from top to bottom in ToolGroupMessage by @imadraude in
|
||||
[#17198](https://github.com/google-gemini/gemini-cli/pull/17198)
|
||||
- fix(cli): treat unknown slash commands as regular input instead of showing
|
||||
error by @skyvanguard in
|
||||
[#17393](https://github.com/google-gemini/gemini-cli/pull/17393)
|
||||
- feat(core): experimental in-progress steering hints (2 of 2) by @joshualitt in
|
||||
[#19307](https://github.com/google-gemini/gemini-cli/pull/19307)
|
||||
- docs(plan): add documentation for plan mode command by @Adib234 in
|
||||
[#19467](https://github.com/google-gemini/gemini-cli/pull/19467)
|
||||
- fix(core): ripgrep fails when pattern looks like ripgrep flag by @syvb in
|
||||
[#18858](https://github.com/google-gemini/gemini-cli/pull/18858)
|
||||
- fix(cli): disable auto-completion on Shift+Tab to preserve mode cycling by
|
||||
@NTaylorMullen in
|
||||
[#19451](https://github.com/google-gemini/gemini-cli/pull/19451)
|
||||
- use issuer instead of authorization_endpoint for oauth discovery by
|
||||
@garrettsparks in
|
||||
[#17332](https://github.com/google-gemini/gemini-cli/pull/17332)
|
||||
- feat(cli): include `/dir add` directories in @ autocomplete suggestions by
|
||||
@jasmeetsb in [#19246](https://github.com/google-gemini/gemini-cli/pull/19246)
|
||||
- feat(admin): Admin settings should only apply if adminControlsApplicable =
|
||||
true and fetch errors should be fatal by @skeshive in
|
||||
[#19453](https://github.com/google-gemini/gemini-cli/pull/19453)
|
||||
- Format strict-development-rules command by @g-samroberts in
|
||||
[#19484](https://github.com/google-gemini/gemini-cli/pull/19484)
|
||||
- feat(core): centralize compatibility checks and add TrueColor detection by
|
||||
@spencer426 in
|
||||
[#19478](https://github.com/google-gemini/gemini-cli/pull/19478)
|
||||
- Remove unused files and update index and sidebar. by @g-samroberts in
|
||||
[#19479](https://github.com/google-gemini/gemini-cli/pull/19479)
|
||||
- Migrate core render util to use xterm.js as part of the rendering loop. by
|
||||
@jacob314 in [#19044](https://github.com/google-gemini/gemini-cli/pull/19044)
|
||||
- Changelog for v0.30.0-preview.1 by @gemini-cli-robot in
|
||||
[#19496](https://github.com/google-gemini/gemini-cli/pull/19496)
|
||||
- build: replace deprecated built-in punycode with userland package by @jacob314
|
||||
in [#19502](https://github.com/google-gemini/gemini-cli/pull/19502)
|
||||
- Speculative fixes to try to fix react error. by @jacob314 in
|
||||
[#19508](https://github.com/google-gemini/gemini-cli/pull/19508)
|
||||
- fix spacing by @jacob314 in
|
||||
[#19494](https://github.com/google-gemini/gemini-cli/pull/19494)
|
||||
- fix(core): ensure user rejections update tool outcome for telemetry by
|
||||
@abhiasap in [#18982](https://github.com/google-gemini/gemini-cli/pull/18982)
|
||||
- fix(acp): Initialize config (#18897) by @Mervap in
|
||||
[#18898](https://github.com/google-gemini/gemini-cli/pull/18898)
|
||||
- fix(core): add error logging for IDE fetch failures by @yuvrajangadsingh in
|
||||
[#17981](https://github.com/google-gemini/gemini-cli/pull/17981)
|
||||
- feat(acp): support set_mode interface (#18890) by @Mervap in
|
||||
[#18891](https://github.com/google-gemini/gemini-cli/pull/18891)
|
||||
- fix(core): robust workspace-based IDE connection discovery by @ehedlund in
|
||||
[#18443](https://github.com/google-gemini/gemini-cli/pull/18443)
|
||||
- Deflake windows tests. by @jacob314 in
|
||||
[#19511](https://github.com/google-gemini/gemini-cli/pull/19511)
|
||||
- Fix: Avoid tool confirmation timeout when no UI listeners are present by
|
||||
@pdHaku0 in [#17955](https://github.com/google-gemini/gemini-cli/pull/17955)
|
||||
- format md file by @scidomino in
|
||||
[#19474](https://github.com/google-gemini/gemini-cli/pull/19474)
|
||||
- feat(cli): add experimental.useOSC52Copy setting by @scidomino in
|
||||
[#19488](https://github.com/google-gemini/gemini-cli/pull/19488)
|
||||
- feat(cli): replace loading phrases boolean with enum setting by @LyalinDotCom
|
||||
in [#19347](https://github.com/google-gemini/gemini-cli/pull/19347)
|
||||
- Update skill to adjust for generated results. by @g-samroberts in
|
||||
[#19500](https://github.com/google-gemini/gemini-cli/pull/19500)
|
||||
- Fix message too large issue. by @gundermanc in
|
||||
[#19499](https://github.com/google-gemini/gemini-cli/pull/19499)
|
||||
- fix(core): prevent duplicate tool approval entries in auto-saved.toml by
|
||||
@Abhijit-2592 in
|
||||
[#19487](https://github.com/google-gemini/gemini-cli/pull/19487)
|
||||
- fix(core): resolve crash in ClearcutLogger when os.cpus() is empty by @Adib234
|
||||
in [#19555](https://github.com/google-gemini/gemini-cli/pull/19555)
|
||||
- chore(core): improve encapsulation and remove unused exports by @adamfweidman
|
||||
in [#19556](https://github.com/google-gemini/gemini-cli/pull/19556)
|
||||
- Revert "Add generic searchable list to back settings and extensions (… by
|
||||
@chrstnb in [#19434](https://github.com/google-gemini/gemini-cli/pull/19434)
|
||||
- fix(core): improve error type extraction for telemetry by @yunaseoul in
|
||||
[#19565](https://github.com/google-gemini/gemini-cli/pull/19565)
|
||||
- fix: remove extra padding in Composer by @jackwotherspoon in
|
||||
[#19529](https://github.com/google-gemini/gemini-cli/pull/19529)
|
||||
- feat(plan): support configuring custom plans storage directory by @jerop in
|
||||
[#19577](https://github.com/google-gemini/gemini-cli/pull/19577)
|
||||
- Migrate files to resource or references folder. by @g-samroberts in
|
||||
[#19503](https://github.com/google-gemini/gemini-cli/pull/19503)
|
||||
- feat(policy): implement project-level policy support by @Abhijit-2592 in
|
||||
[#18682](https://github.com/google-gemini/gemini-cli/pull/18682)
|
||||
- feat(core): Implement parallel FC for read only tools. by @joshualitt in
|
||||
[#18791](https://github.com/google-gemini/gemini-cli/pull/18791)
|
||||
- chore(skills): adds pr-address-comments skill to work on PR feedback by
|
||||
@mbleigh in [#19576](https://github.com/google-gemini/gemini-cli/pull/19576)
|
||||
- refactor(sdk): introduce session-based architecture by @mbleigh in
|
||||
[#19180](https://github.com/google-gemini/gemini-cli/pull/19180)
|
||||
- fix(ci): add fallback JSON extraction to issue triage workflow by @bdmorgan in
|
||||
[#19593](https://github.com/google-gemini/gemini-cli/pull/19593)
|
||||
- feat(core): refine Edit and WriteFile tool schemas for Gemini 3 by
|
||||
[#18586](https://github.com/google-gemini/gemini-cli/pull/18586)
|
||||
- fix(core): cache CLI version to ensure consistency during sessions by
|
||||
@sehoon38 in [#18793](https://github.com/google-gemini/gemini-cli/pull/18793)
|
||||
- fix(cli): resolve double rendering in shpool and address vscode lint warnings
|
||||
by @braddux in
|
||||
[#18704](https://github.com/google-gemini/gemini-cli/pull/18704)
|
||||
- feat(plan): document and validate Plan Mode policy overrides by @jerop in
|
||||
[#18825](https://github.com/google-gemini/gemini-cli/pull/18825)
|
||||
- Fix pressing any key to exit select mode. by @jacob314 in
|
||||
[#18421](https://github.com/google-gemini/gemini-cli/pull/18421)
|
||||
- fix(cli): update F12 behavior to only open drawer if browser fails by
|
||||
@SandyTao520 in
|
||||
[#19476](https://github.com/google-gemini/gemini-cli/pull/19476)
|
||||
- Changelog for v0.30.0-preview.3 by @gemini-cli-robot in
|
||||
[#19585](https://github.com/google-gemini/gemini-cli/pull/19585)
|
||||
- fix(plan): exclude EnterPlanMode tool from YOLO mode by @Adib234 in
|
||||
[#19570](https://github.com/google-gemini/gemini-cli/pull/19570)
|
||||
- chore: resolve build warnings and update dependencies by @mattKorwel in
|
||||
[#18880](https://github.com/google-gemini/gemini-cli/pull/18880)
|
||||
- feat(ui): add source indicators to slash commands by @ehedlund in
|
||||
[#18839](https://github.com/google-gemini/gemini-cli/pull/18839)
|
||||
- docs: refine Plan Mode documentation structure and workflow by @jerop in
|
||||
[#19644](https://github.com/google-gemini/gemini-cli/pull/19644)
|
||||
- Docs: Update release information regarding Gemini 3.1 by @jkcinouye in
|
||||
[#19568](https://github.com/google-gemini/gemini-cli/pull/19568)
|
||||
- fix(security): rate limit web_fetch tool to mitigate DDoS via prompt injection
|
||||
by @mattKorwel in
|
||||
[#19567](https://github.com/google-gemini/gemini-cli/pull/19567)
|
||||
- Add initial implementation of /extensions explore command by @chrstnb in
|
||||
[#19029](https://github.com/google-gemini/gemini-cli/pull/19029)
|
||||
- fix: use discoverOAuthFromWWWAuthenticate for reactive OAuth flow (#18760) by
|
||||
@maximus12793 in
|
||||
[#19038](https://github.com/google-gemini/gemini-cli/pull/19038)
|
||||
- Search updates by @alisa-alisa in
|
||||
[#19482](https://github.com/google-gemini/gemini-cli/pull/19482)
|
||||
- feat(cli): add support for numpad SS3 sequences by @scidomino in
|
||||
[#19659](https://github.com/google-gemini/gemini-cli/pull/19659)
|
||||
- feat(cli): enhance folder trust with configuration discovery and security
|
||||
warnings by @galz10 in
|
||||
[#19492](https://github.com/google-gemini/gemini-cli/pull/19492)
|
||||
- feat(ui): improve startup warnings UX with dismissal and show-count limits by
|
||||
@spencer426 in
|
||||
[#19584](https://github.com/google-gemini/gemini-cli/pull/19584)
|
||||
- feat(a2a): Add API key authentication provider by @adamfweidman in
|
||||
[#19548](https://github.com/google-gemini/gemini-cli/pull/19548)
|
||||
- Send accepted/removed lines with ACCEPT_FILE telemetry. by @gundermanc in
|
||||
[#19670](https://github.com/google-gemini/gemini-cli/pull/19670)
|
||||
- feat(models): support Gemini 3.1 Pro Preview and fixes by @sehoon38 in
|
||||
[#19676](https://github.com/google-gemini/gemini-cli/pull/19676)
|
||||
- feat(plan): enforce read-only constraints in Plan Mode by @mattKorwel in
|
||||
[#19433](https://github.com/google-gemini/gemini-cli/pull/19433)
|
||||
- fix(cli): allow perfect match @scripts/test-windows-paths.js completions to
|
||||
submit on Enter by @spencer426 in
|
||||
[#19562](https://github.com/google-gemini/gemini-cli/pull/19562)
|
||||
- fix(core): treat 503 Service Unavailable as retryable quota error by @sehoon38
|
||||
in [#19642](https://github.com/google-gemini/gemini-cli/pull/19642)
|
||||
- Update sidebar.json for to allow top nav tabs. by @g-samroberts in
|
||||
[#19595](https://github.com/google-gemini/gemini-cli/pull/19595)
|
||||
- security: strip deceptive Unicode characters from terminal output by @ehedlund
|
||||
in [#19026](https://github.com/google-gemini/gemini-cli/pull/19026)
|
||||
- Fixes 'input.on' is not a function error in Gemini CLI by @gundermanc in
|
||||
[#19691](https://github.com/google-gemini/gemini-cli/pull/19691)
|
||||
- Revert "feat(ui): add source indicators to slash commands" by @ehedlund in
|
||||
[#19695](https://github.com/google-gemini/gemini-cli/pull/19695)
|
||||
- security: implement deceptive URL detection and disclosure in tool
|
||||
confirmations by @ehedlund in
|
||||
[#19288](https://github.com/google-gemini/gemini-cli/pull/19288)
|
||||
- fix(core): restore auth consent in headless mode and add unit tests by
|
||||
@ehedlund in [#19689](https://github.com/google-gemini/gemini-cli/pull/19689)
|
||||
- Fix unsafe assertions in code_assist folder. by @gundermanc in
|
||||
[#19706](https://github.com/google-gemini/gemini-cli/pull/19706)
|
||||
- feat(cli): make JetBrains warning more specific by @jacob314 in
|
||||
[#19687](https://github.com/google-gemini/gemini-cli/pull/19687)
|
||||
- fix(cli): extensions dialog UX polish by @jacob314 in
|
||||
[#19685](https://github.com/google-gemini/gemini-cli/pull/19685)
|
||||
- fix(cli): use getDisplayString for manual model selection in dialog by
|
||||
@sehoon38 in [#19726](https://github.com/google-gemini/gemini-cli/pull/19726)
|
||||
- feat(policy): repurpose "Always Allow" persistence to workspace level by
|
||||
@Abhijit-2592 in
|
||||
[#19707](https://github.com/google-gemini/gemini-cli/pull/19707)
|
||||
- fix(cli): re-enable CLI banner by @sehoon38 in
|
||||
[#19741](https://github.com/google-gemini/gemini-cli/pull/19741)
|
||||
- Disallow and suppress unsafe assignment by @gundermanc in
|
||||
[#19736](https://github.com/google-gemini/gemini-cli/pull/19736)
|
||||
- feat(core): migrate read_file to 1-based start_line/end_line parameters by
|
||||
@adamfweidman in
|
||||
[#19526](https://github.com/google-gemini/gemini-cli/pull/19526)
|
||||
- feat(cli): improve CTRL+O experience for both standard and alternate screen
|
||||
buffer (ASB) modes by @jwhelangoog in
|
||||
[#19010](https://github.com/google-gemini/gemini-cli/pull/19010)
|
||||
- Utilize pipelining of grep_search -> read_file to eliminate turns by
|
||||
@gundermanc in
|
||||
[#19574](https://github.com/google-gemini/gemini-cli/pull/19574)
|
||||
- refactor(core): remove unsafe type assertions in error utils (Phase 1.1) by
|
||||
[#18829](https://github.com/google-gemini/gemini-cli/pull/18829)
|
||||
- feat(plan): allow skills to be enabled in plan mode by @Adib234 in
|
||||
[#18817](https://github.com/google-gemini/gemini-cli/pull/18817)
|
||||
- docs(plan): add documentation for plan mode tools by @jerop in
|
||||
[#18827](https://github.com/google-gemini/gemini-cli/pull/18827)
|
||||
- Remove experimental note in extension settings docs by @chrstnb in
|
||||
[#18822](https://github.com/google-gemini/gemini-cli/pull/18822)
|
||||
- Update prompt and grep tool definition to limit context size by @gundermanc in
|
||||
[#18780](https://github.com/google-gemini/gemini-cli/pull/18780)
|
||||
- docs(plan): add `ask_user` tool documentation by @jerop in
|
||||
[#18830](https://github.com/google-gemini/gemini-cli/pull/18830)
|
||||
- Revert unintended credentials exposure by @Adib234 in
|
||||
[#18840](https://github.com/google-gemini/gemini-cli/pull/18840)
|
||||
- feat(core): update internal utility models to Gemini 3 by @SandyTao520 in
|
||||
[#18773](https://github.com/google-gemini/gemini-cli/pull/18773)
|
||||
- feat(a2a): add value-resolver for auth credential resolution by @adamfweidman
|
||||
in [#18653](https://github.com/google-gemini/gemini-cli/pull/18653)
|
||||
- Removed getPlainTextLength by @devr0306 in
|
||||
[#18848](https://github.com/google-gemini/gemini-cli/pull/18848)
|
||||
- More grep prompt tweaks by @gundermanc in
|
||||
[#18846](https://github.com/google-gemini/gemini-cli/pull/18846)
|
||||
- refactor(cli): Reactive useSettingsStore hook by @psinha40898 in
|
||||
[#14915](https://github.com/google-gemini/gemini-cli/pull/14915)
|
||||
- fix(mcp): Ensure that stdio MCP server execution has the `GEMINI_CLI=1` env
|
||||
variable populated. by @richieforeman in
|
||||
[#18832](https://github.com/google-gemini/gemini-cli/pull/18832)
|
||||
- fix(core): improve headless mode detection for flags and query args by @galz10
|
||||
in [#18855](https://github.com/google-gemini/gemini-cli/pull/18855)
|
||||
- refactor(cli): simplify UI and remove legacy inline tool confirmation logic by
|
||||
@abhipatel12 in
|
||||
[#18566](https://github.com/google-gemini/gemini-cli/pull/18566)
|
||||
- feat(cli): deprecate --allowed-tools and excludeTools in favor of policy
|
||||
engine by @Abhijit-2592 in
|
||||
[#18508](https://github.com/google-gemini/gemini-cli/pull/18508)
|
||||
- fix(workflows): improve maintainer detection for automated PR actions by
|
||||
@bdmorgan in [#18869](https://github.com/google-gemini/gemini-cli/pull/18869)
|
||||
- refactor(cli): consolidate useToolScheduler and delete legacy implementation
|
||||
by @abhipatel12 in
|
||||
[#18567](https://github.com/google-gemini/gemini-cli/pull/18567)
|
||||
- Update changelog for v0.28.0 and v0.29.0-preview0 by @g-samroberts in
|
||||
[#18819](https://github.com/google-gemini/gemini-cli/pull/18819)
|
||||
- fix(core): ensure sub-agents are registered regardless of tools.allowed by
|
||||
@mattKorwel in
|
||||
[#19750](https://github.com/google-gemini/gemini-cli/pull/19750)
|
||||
- Disallow unsafe returns. by @gundermanc in
|
||||
[#19767](https://github.com/google-gemini/gemini-cli/pull/19767)
|
||||
- fix(cli): filter subagent sessions from resume history by @abhipatel12 in
|
||||
[#19698](https://github.com/google-gemini/gemini-cli/pull/19698)
|
||||
- chore(lint): fix lint errors seen when running npm run lint by @abhipatel12 in
|
||||
[#19844](https://github.com/google-gemini/gemini-cli/pull/19844)
|
||||
- feat(core): remove unnecessary login verbiage from Code Assist auth by
|
||||
@NTaylorMullen in
|
||||
[#19861](https://github.com/google-gemini/gemini-cli/pull/19861)
|
||||
- fix(plan): time share by approval mode dashboard reporting negative time
|
||||
shares by @Adib234 in
|
||||
[#19847](https://github.com/google-gemini/gemini-cli/pull/19847)
|
||||
- fix(core): allow any preview model in quota access check by @bdmorgan in
|
||||
[#19867](https://github.com/google-gemini/gemini-cli/pull/19867)
|
||||
- fix(core): prevent omission placeholder deletions in replace/write_file by
|
||||
@nsalerni in [#19870](https://github.com/google-gemini/gemini-cli/pull/19870)
|
||||
- fix(core): add uniqueness guard to edit tool by @Shivangisharma4 in
|
||||
[#19890](https://github.com/google-gemini/gemini-cli/pull/19890)
|
||||
- refactor(config): remove enablePromptCompletion from settings by @sehoon38 in
|
||||
[#19974](https://github.com/google-gemini/gemini-cli/pull/19974)
|
||||
- refactor(core): move session conversion logic to core by @abhipatel12 in
|
||||
[#19972](https://github.com/google-gemini/gemini-cli/pull/19972)
|
||||
- Fix: Persist manual model selection on restart #19864 by @Nixxx19 in
|
||||
[#19891](https://github.com/google-gemini/gemini-cli/pull/19891)
|
||||
- fix(core): increase default retry attempts and add quota error backoff by
|
||||
@sehoon38 in [#19949](https://github.com/google-gemini/gemini-cli/pull/19949)
|
||||
- feat(core): add policy chain support for Gemini 3.1 by @sehoon38 in
|
||||
[#19991](https://github.com/google-gemini/gemini-cli/pull/19991)
|
||||
- Updates command reference and /stats command. by @g-samroberts in
|
||||
[#19794](https://github.com/google-gemini/gemini-cli/pull/19794)
|
||||
- Fix for silent failures in non-interactive mode by @owenofbrien in
|
||||
[#19905](https://github.com/google-gemini/gemini-cli/pull/19905)
|
||||
- fix(plan): allow plan mode writes on Windows and fix prompt paths by @Adib234
|
||||
in [#19658](https://github.com/google-gemini/gemini-cli/pull/19658)
|
||||
- fix(core): prevent OAuth server crash on unexpected requests by @reyyanxahmed
|
||||
in [#19668](https://github.com/google-gemini/gemini-cli/pull/19668)
|
||||
- feat: Map tool kinds to explicit ACP.ToolKind values and update test … by
|
||||
@sripasg in [#19547](https://github.com/google-gemini/gemini-cli/pull/19547)
|
||||
- chore: restrict gemini-automted-issue-triage to only allow echo by @galz10 in
|
||||
[#20047](https://github.com/google-gemini/gemini-cli/pull/20047)
|
||||
- Allow ask headers longer than 16 chars by @scidomino in
|
||||
[#20041](https://github.com/google-gemini/gemini-cli/pull/20041)
|
||||
- fix(core): prevent state corruption in McpClientManager during collis by @h30s
|
||||
in [#19782](https://github.com/google-gemini/gemini-cli/pull/19782)
|
||||
- fix(bundling): copy devtools package to bundle for runtime resolution by
|
||||
@SandyTao520 in
|
||||
[#19766](https://github.com/google-gemini/gemini-cli/pull/19766)
|
||||
- feat(policy): Support MCP Server Wildcards in Policy Engine by @jerop in
|
||||
[#20024](https://github.com/google-gemini/gemini-cli/pull/20024)
|
||||
- docs(CONTRIBUTING): update React DevTools version to 6 by @mmgok in
|
||||
[#20014](https://github.com/google-gemini/gemini-cli/pull/20014)
|
||||
- feat(core): optimize tool descriptions and schemas for Gemini 3 by
|
||||
@aishaneeshah in
|
||||
[#19643](https://github.com/google-gemini/gemini-cli/pull/19643)
|
||||
- feat(core): implement experimental direct web fetch by @mbleigh in
|
||||
[#19557](https://github.com/google-gemini/gemini-cli/pull/19557)
|
||||
- feat(core): replace expected_replacements with allow_multiple in replace tool
|
||||
by @SandyTao520 in
|
||||
[#20033](https://github.com/google-gemini/gemini-cli/pull/20033)
|
||||
- fix(sandbox): harden image packaging integrity checks by @aviralgarg05 in
|
||||
[#19552](https://github.com/google-gemini/gemini-cli/pull/19552)
|
||||
- fix(core): allow environment variable expansion and explicit overrides for MCP
|
||||
servers by @galz10 in
|
||||
[#18837](https://github.com/google-gemini/gemini-cli/pull/18837)
|
||||
- feat(policy): Implement Tool Annotation Matching in Policy Engine by @jerop in
|
||||
[#20029](https://github.com/google-gemini/gemini-cli/pull/20029)
|
||||
- fix(core): prevent utility calls from changing session active model by
|
||||
@adamfweidman in
|
||||
[#20035](https://github.com/google-gemini/gemini-cli/pull/20035)
|
||||
- fix(cli): skip workspace policy loading when in home directory by
|
||||
[#18870](https://github.com/google-gemini/gemini-cli/pull/18870)
|
||||
- Show notification when there's a conflict with an extensions command by
|
||||
@chrstnb in [#17890](https://github.com/google-gemini/gemini-cli/pull/17890)
|
||||
- fix(cli): dismiss '?' shortcuts help on hotkeys and active states by
|
||||
@LyalinDotCom in
|
||||
[#18583](https://github.com/google-gemini/gemini-cli/pull/18583)
|
||||
- fix(core): prioritize conditional policy rules and harden Plan Mode by
|
||||
@Abhijit-2592 in
|
||||
[#20054](https://github.com/google-gemini/gemini-cli/pull/20054)
|
||||
- fix(scripts): Add Windows (win32/x64) support to lint.js by @ZafeerMahmood in
|
||||
[#16193](https://github.com/google-gemini/gemini-cli/pull/16193)
|
||||
- fix(a2a-server): Remove unsafe type assertions in agent by @Nixxx19 in
|
||||
[#19723](https://github.com/google-gemini/gemini-cli/pull/19723)
|
||||
- Fix: Handle corrupted token file gracefully when switching auth types (#19845)
|
||||
by @Nixxx19 in
|
||||
[#19850](https://github.com/google-gemini/gemini-cli/pull/19850)
|
||||
- fix critical dep vulnerability by @scidomino in
|
||||
[#20087](https://github.com/google-gemini/gemini-cli/pull/20087)
|
||||
- Add new setting to configure maxRetries by @kevinjwang1 in
|
||||
[#20064](https://github.com/google-gemini/gemini-cli/pull/20064)
|
||||
- Stabilize tests. by @gundermanc in
|
||||
[#20095](https://github.com/google-gemini/gemini-cli/pull/20095)
|
||||
- make windows tests mandatory by @scidomino in
|
||||
[#20096](https://github.com/google-gemini/gemini-cli/pull/20096)
|
||||
- Add 3.1 pro preview to behavioral evals. by @gundermanc in
|
||||
[#20088](https://github.com/google-gemini/gemini-cli/pull/20088)
|
||||
- feat:PR-rate-limit by @JagjeevanAK in
|
||||
[#19804](https://github.com/google-gemini/gemini-cli/pull/19804)
|
||||
- feat(cli): allow expanding full details of MCP tool on approval by @y-okt in
|
||||
[#19916](https://github.com/google-gemini/gemini-cli/pull/19916)
|
||||
- feat(security): Introduce Conseca framework by @shrishabh in
|
||||
[#13193](https://github.com/google-gemini/gemini-cli/pull/13193)
|
||||
- fix(cli): Remove unsafe type assertions in activityLogger #19713 by @Nixxx19
|
||||
in [#19745](https://github.com/google-gemini/gemini-cli/pull/19745)
|
||||
- feat: implement AfterTool tail tool calls by @googlestrobe in
|
||||
[#18486](https://github.com/google-gemini/gemini-cli/pull/18486)
|
||||
- ci(actions): fix PR rate limiter excluding maintainers by @scidomino in
|
||||
[#20117](https://github.com/google-gemini/gemini-cli/pull/20117)
|
||||
- Shortcuts: Move SectionHeader title below top line and refine styling by
|
||||
@keithguerin in
|
||||
[#18721](https://github.com/google-gemini/gemini-cli/pull/18721)
|
||||
- refactor(ui): Update and simplify use of gray colors in themes by @keithguerin
|
||||
in [#20141](https://github.com/google-gemini/gemini-cli/pull/20141)
|
||||
- fix punycode2 by @jacob314 in
|
||||
[#20154](https://github.com/google-gemini/gemini-cli/pull/20154)
|
||||
- feat(ide): add GEMINI_CLI_IDE_PID env var to override IDE process detection by
|
||||
@kiryltech in [#15842](https://github.com/google-gemini/gemini-cli/pull/15842)
|
||||
- feat(policy): Propagate Tool Annotations for MCP Servers by @jerop in
|
||||
[#20083](https://github.com/google-gemini/gemini-cli/pull/20083)
|
||||
- fix(a2a-server): pass allowedTools settings to core Config by @reyyanxahmed in
|
||||
[#19680](https://github.com/google-gemini/gemini-cli/pull/19680)
|
||||
- feat(mcp): add progress bar, throttling, and input validation for MCP tool
|
||||
progress by @jasmeetsb in
|
||||
[#19772](https://github.com/google-gemini/gemini-cli/pull/19772)
|
||||
- feat(policy): centralize plan mode tool visibility in policy engine by @jerop
|
||||
in [#20178](https://github.com/google-gemini/gemini-cli/pull/20178)
|
||||
- feat(browser): implement experimental browser agent by @gsquared94 in
|
||||
[#19284](https://github.com/google-gemini/gemini-cli/pull/19284)
|
||||
- feat(plan): summarize work after executing a plan by @jerop in
|
||||
[#19432](https://github.com/google-gemini/gemini-cli/pull/19432)
|
||||
- fix(core): create new McpClient on restart to apply updated config by @h30s in
|
||||
[#20126](https://github.com/google-gemini/gemini-cli/pull/20126)
|
||||
- Changelog for v0.30.0-preview.5 by @gemini-cli-robot in
|
||||
[#20107](https://github.com/google-gemini/gemini-cli/pull/20107)
|
||||
- Update packages. by @jacob314 in
|
||||
[#20152](https://github.com/google-gemini/gemini-cli/pull/20152)
|
||||
- Fix extension env dir loading issue by @chrstnb in
|
||||
[#20198](https://github.com/google-gemini/gemini-cli/pull/20198)
|
||||
- restrict /assign to help-wanted issues by @scidomino in
|
||||
[#20207](https://github.com/google-gemini/gemini-cli/pull/20207)
|
||||
- feat(plan): inject message when user manually exits Plan mode by @jerop in
|
||||
[#20203](https://github.com/google-gemini/gemini-cli/pull/20203)
|
||||
- feat(extensions): enforce folder trust for local extension install by @galz10
|
||||
in [#19703](https://github.com/google-gemini/gemini-cli/pull/19703)
|
||||
- feat(hooks): adds support for RuntimeHook functions. by @mbleigh in
|
||||
[#19598](https://github.com/google-gemini/gemini-cli/pull/19598)
|
||||
- Docs: Update UI links. by @jkcinouye in
|
||||
[#20224](https://github.com/google-gemini/gemini-cli/pull/20224)
|
||||
- feat: prompt users to run /terminal-setup with yes/no by @ishaanxgupta in
|
||||
[#16235](https://github.com/google-gemini/gemini-cli/pull/16235)
|
||||
- fix: additional high vulnerabilities (minimatch, cross-spawn) by @adamfweidman
|
||||
in [#20221](https://github.com/google-gemini/gemini-cli/pull/20221)
|
||||
- feat(telemetry): Add context breakdown to API response event by @SandyTao520
|
||||
in [#19699](https://github.com/google-gemini/gemini-cli/pull/19699)
|
||||
- Docs: Add nested sub-folders for related topics by @g-samroberts in
|
||||
[#20235](https://github.com/google-gemini/gemini-cli/pull/20235)
|
||||
- feat(plan): support automatic model switching for Plan Mode by @jerop in
|
||||
[#20240](https://github.com/google-gemini/gemini-cli/pull/20240)
|
||||
- fix(patch): cherry-pick 58df1c6 to release/v0.31.0-preview.0-pr-20374 to patch
|
||||
version v0.31.0-preview.0 and create version 0.31.0-preview.1 by
|
||||
[#18882](https://github.com/google-gemini/gemini-cli/pull/18882)
|
||||
- feat(core): refine Plan Mode system prompt for agentic execution by
|
||||
@NTaylorMullen in
|
||||
[#18799](https://github.com/google-gemini/gemini-cli/pull/18799)
|
||||
- feat(plan): create metrics for usage of `AskUser` tool by @Adib234 in
|
||||
[#18820](https://github.com/google-gemini/gemini-cli/pull/18820)
|
||||
- feat(cli): support Ctrl-Z suspension by @scidomino in
|
||||
[#18931](https://github.com/google-gemini/gemini-cli/pull/18931)
|
||||
- fix(github-actions): use robot PAT for release creation to trigger release
|
||||
notes by @SandyTao520 in
|
||||
[#18794](https://github.com/google-gemini/gemini-cli/pull/18794)
|
||||
- feat: add strict seatbelt profiles and remove unusable closed profiles by
|
||||
@SandyTao520 in
|
||||
[#18876](https://github.com/google-gemini/gemini-cli/pull/18876)
|
||||
- chore: cleanup unused and add unlisted dependencies in packages/a2a-server by
|
||||
@adamfweidman in
|
||||
[#18916](https://github.com/google-gemini/gemini-cli/pull/18916)
|
||||
- fix(plan): isolate plan files per session by @Adib234 in
|
||||
[#18757](https://github.com/google-gemini/gemini-cli/pull/18757)
|
||||
- fix: character truncation in raw markdown mode by @jackwotherspoon in
|
||||
[#18938](https://github.com/google-gemini/gemini-cli/pull/18938)
|
||||
- feat(cli): prototype clean UI toggle and minimal-mode bleed-through by
|
||||
@LyalinDotCom in
|
||||
[#18683](https://github.com/google-gemini/gemini-cli/pull/18683)
|
||||
- ui(polish) blend background color with theme by @jacob314 in
|
||||
[#18802](https://github.com/google-gemini/gemini-cli/pull/18802)
|
||||
- Add generic searchable list to back settings and extensions by @chrstnb in
|
||||
[#18838](https://github.com/google-gemini/gemini-cli/pull/18838)
|
||||
- feat(ui): align `AskUser` color scheme with UX spec by @jerop in
|
||||
[#18943](https://github.com/google-gemini/gemini-cli/pull/18943)
|
||||
- Hide AskUser tool validation errors from UI (agent self-corrects) by @jerop in
|
||||
[#18954](https://github.com/google-gemini/gemini-cli/pull/18954)
|
||||
- bug(cli) fix flicker due to AppContainer continuous initialization by
|
||||
@jacob314 in [#18958](https://github.com/google-gemini/gemini-cli/pull/18958)
|
||||
- feat(admin): Add admin controls documentation by @skeshive in
|
||||
[#18644](https://github.com/google-gemini/gemini-cli/pull/18644)
|
||||
- feat(cli): disable ctrl-s shortcut outside of alternate buffer mode by
|
||||
@jacob314 in [#18887](https://github.com/google-gemini/gemini-cli/pull/18887)
|
||||
- fix(vim): vim support that feels (more) complete by @ppgranger in
|
||||
[#18755](https://github.com/google-gemini/gemini-cli/pull/18755)
|
||||
- feat(policy): add --policy flag for user defined policies by @allenhutchison
|
||||
in [#18500](https://github.com/google-gemini/gemini-cli/pull/18500)
|
||||
- Update installation guide by @g-samroberts in
|
||||
[#18823](https://github.com/google-gemini/gemini-cli/pull/18823)
|
||||
- refactor(core): centralize tool definitions (Group 1: replace, search, grep)
|
||||
by @aishaneeshah in
|
||||
[#18944](https://github.com/google-gemini/gemini-cli/pull/18944)
|
||||
- refactor(cli): finalize event-driven transition and remove interaction bridge
|
||||
by @abhipatel12 in
|
||||
[#18569](https://github.com/google-gemini/gemini-cli/pull/18569)
|
||||
- Fix drag and drop escaping by @scidomino in
|
||||
[#18965](https://github.com/google-gemini/gemini-cli/pull/18965)
|
||||
- feat(sdk): initial package bootstrap for SDK by @mbleigh in
|
||||
[#18861](https://github.com/google-gemini/gemini-cli/pull/18861)
|
||||
- feat(sdk): implements SessionContext for SDK tool calls by @mbleigh in
|
||||
[#18862](https://github.com/google-gemini/gemini-cli/pull/18862)
|
||||
- fix(plan): make question type required in AskUser tool by @Adib234 in
|
||||
[#18959](https://github.com/google-gemini/gemini-cli/pull/18959)
|
||||
- fix(core): ensure --yolo does not force headless mode by @NTaylorMullen in
|
||||
[#18976](https://github.com/google-gemini/gemini-cli/pull/18976)
|
||||
- refactor(core): adopt `CoreToolCallStatus` enum for type safety by @jerop in
|
||||
[#18998](https://github.com/google-gemini/gemini-cli/pull/18998)
|
||||
- Enable in-CLI extension management commands for team by @chrstnb in
|
||||
[#18957](https://github.com/google-gemini/gemini-cli/pull/18957)
|
||||
- Adjust lint rules to avoid unnecessary warning. by @scidomino in
|
||||
[#18970](https://github.com/google-gemini/gemini-cli/pull/18970)
|
||||
- fix(vscode): resolve unsafe type assertion lint errors by @ehedlund in
|
||||
[#19006](https://github.com/google-gemini/gemini-cli/pull/19006)
|
||||
- Remove unnecessary eslint config file by @scidomino in
|
||||
[#19015](https://github.com/google-gemini/gemini-cli/pull/19015)
|
||||
- fix(core): Prevent loop detection false positives on lists with long shared
|
||||
prefixes by @SandyTao520 in
|
||||
[#18975](https://github.com/google-gemini/gemini-cli/pull/18975)
|
||||
- feat(core): fallback to chat-base when using unrecognized models for chat by
|
||||
@SandyTao520 in
|
||||
[#19016](https://github.com/google-gemini/gemini-cli/pull/19016)
|
||||
- docs: fix inconsistent commandRegex example in policy engine by @NTaylorMullen
|
||||
in [#19027](https://github.com/google-gemini/gemini-cli/pull/19027)
|
||||
- fix(plan): persist the approval mode in UI even when agent is thinking by
|
||||
@Adib234 in [#18955](https://github.com/google-gemini/gemini-cli/pull/18955)
|
||||
- feat(sdk): Implement dynamic system instructions by @mbleigh in
|
||||
[#18863](https://github.com/google-gemini/gemini-cli/pull/18863)
|
||||
- Docs: Refresh docs to organize and standardize reference materials. by
|
||||
@jkcinouye in [#18403](https://github.com/google-gemini/gemini-cli/pull/18403)
|
||||
- fix windows escaping (and broken tests) by @scidomino in
|
||||
[#19011](https://github.com/google-gemini/gemini-cli/pull/19011)
|
||||
- refactor: use `CoreToolCallStatus` in the the history data model by @jerop in
|
||||
[#19033](https://github.com/google-gemini/gemini-cli/pull/19033)
|
||||
- feat(cleanup): enable 30-day session retention by default by @skeshive in
|
||||
[#18854](https://github.com/google-gemini/gemini-cli/pull/18854)
|
||||
- feat(plan): hide plan write and edit operations on plans in Plan Mode by
|
||||
@jerop in [#19012](https://github.com/google-gemini/gemini-cli/pull/19012)
|
||||
- bug(ui) fix flicker refreshing background color by @jacob314 in
|
||||
[#19041](https://github.com/google-gemini/gemini-cli/pull/19041)
|
||||
- chore: fix dep vulnerabilities by @scidomino in
|
||||
[#19036](https://github.com/google-gemini/gemini-cli/pull/19036)
|
||||
- Revamp automated changelog skill by @g-samroberts in
|
||||
[#18974](https://github.com/google-gemini/gemini-cli/pull/18974)
|
||||
- feat(sdk): implement support for custom skills by @mbleigh in
|
||||
[#19031](https://github.com/google-gemini/gemini-cli/pull/19031)
|
||||
- refactor(core): complete centralization of core tool definitions by
|
||||
@aishaneeshah in
|
||||
[#18991](https://github.com/google-gemini/gemini-cli/pull/18991)
|
||||
- feat: add /commands reload to refresh custom TOML commands by @korade-krushna
|
||||
in [#19078](https://github.com/google-gemini/gemini-cli/pull/19078)
|
||||
- fix(cli): wrap terminal capability queries in hidden sequence by @srithreepo
|
||||
in [#19080](https://github.com/google-gemini/gemini-cli/pull/19080)
|
||||
- fix(workflows): fix GitHub App token permissions for maintainer detection by
|
||||
@bdmorgan in [#19139](https://github.com/google-gemini/gemini-cli/pull/19139)
|
||||
- test: fix hook integration test flakiness on Windows CI by @NTaylorMullen in
|
||||
[#18665](https://github.com/google-gemini/gemini-cli/pull/18665)
|
||||
- fix(core): Encourage non-interactive flags for scaffolding commands by
|
||||
@NTaylorMullen in
|
||||
[#18804](https://github.com/google-gemini/gemini-cli/pull/18804)
|
||||
- fix(core): propagate User-Agent header to setup-phase CodeAssist API calls by
|
||||
@gsquared94 in
|
||||
[#19182](https://github.com/google-gemini/gemini-cli/pull/19182)
|
||||
- docs: document .agents/skills alias and discovery precedence by @kevmoo in
|
||||
[#19166](https://github.com/google-gemini/gemini-cli/pull/19166)
|
||||
- feat(cli): add loading state to new agents notification by @sehoon38 in
|
||||
[#19190](https://github.com/google-gemini/gemini-cli/pull/19190)
|
||||
- Add base branch to workflow. by @g-samroberts in
|
||||
[#19189](https://github.com/google-gemini/gemini-cli/pull/19189)
|
||||
- feat(cli): handle invalid model names in useQuotaAndFallback by @sehoon38 in
|
||||
[#19222](https://github.com/google-gemini/gemini-cli/pull/19222)
|
||||
- docs: custom themes in extensions by @jackwotherspoon in
|
||||
[#19219](https://github.com/google-gemini/gemini-cli/pull/19219)
|
||||
- Disable workspace settings when starting GCLI in the home directory. by
|
||||
@kevinjwang1 in
|
||||
[#19034](https://github.com/google-gemini/gemini-cli/pull/19034)
|
||||
- feat(cli): refactor model command to support set and manage subcommands by
|
||||
@sehoon38 in [#19221](https://github.com/google-gemini/gemini-cli/pull/19221)
|
||||
- Add refresh/reload aliases to slash command subcommands by @korade-krushna in
|
||||
[#19218](https://github.com/google-gemini/gemini-cli/pull/19218)
|
||||
- refactor: consolidate development rules and add cli guidelines by @jacob314 in
|
||||
[#19214](https://github.com/google-gemini/gemini-cli/pull/19214)
|
||||
- chore(ui): remove outdated tip about model routing by @sehoon38 in
|
||||
[#19226](https://github.com/google-gemini/gemini-cli/pull/19226)
|
||||
- feat(core): support custom reasoning models by default by @NTaylorMullen in
|
||||
[#19227](https://github.com/google-gemini/gemini-cli/pull/19227)
|
||||
- Add Solarized Dark and Solarized Light themes by @rmedranollamas in
|
||||
[#19064](https://github.com/google-gemini/gemini-cli/pull/19064)
|
||||
- fix(telemetry): replace JSON.stringify with safeJsonStringify in file
|
||||
exporters by @gsquared94 in
|
||||
[#19244](https://github.com/google-gemini/gemini-cli/pull/19244)
|
||||
- feat(telemetry): add keychain availability and token storage metrics by
|
||||
@abhipatel12 in
|
||||
[#18971](https://github.com/google-gemini/gemini-cli/pull/18971)
|
||||
- feat(cli): update approval mode cycle order by @jerop in
|
||||
[#19254](https://github.com/google-gemini/gemini-cli/pull/19254)
|
||||
- refactor(cli): code review cleanup fix for tab+tab by @jacob314 in
|
||||
[#18967](https://github.com/google-gemini/gemini-cli/pull/18967)
|
||||
- feat(plan): support project exploration without planning when in plan mode by
|
||||
@Adib234 in [#18992](https://github.com/google-gemini/gemini-cli/pull/18992)
|
||||
- feat: add role-specific statistics to telemetry and UI (cont. #15234) by
|
||||
@yunaseoul in [#18824](https://github.com/google-gemini/gemini-cli/pull/18824)
|
||||
- feat(cli): remove Plan Mode from rotation when actively working by @jerop in
|
||||
[#19262](https://github.com/google-gemini/gemini-cli/pull/19262)
|
||||
- Fix side breakage where anchors don't work in slugs. by @g-samroberts in
|
||||
[#19261](https://github.com/google-gemini/gemini-cli/pull/19261)
|
||||
- feat(config): add setting to make directory tree context configurable by
|
||||
@kevin-ramdass in
|
||||
[#19053](https://github.com/google-gemini/gemini-cli/pull/19053)
|
||||
- fix(acp): Wait for mcp initialization in acp (#18893) by @Mervap in
|
||||
[#18894](https://github.com/google-gemini/gemini-cli/pull/18894)
|
||||
- docs: format UTC times in releases doc by @pavan-sh in
|
||||
[#18169](https://github.com/google-gemini/gemini-cli/pull/18169)
|
||||
- Docs: Clarify extensions documentation. by @jkcinouye in
|
||||
[#19277](https://github.com/google-gemini/gemini-cli/pull/19277)
|
||||
- refactor(core): modularize tool definitions by model family by @aishaneeshah
|
||||
in [#19269](https://github.com/google-gemini/gemini-cli/pull/19269)
|
||||
- fix(paths): Add cross-platform path normalization by @spencer426 in
|
||||
[#18939](https://github.com/google-gemini/gemini-cli/pull/18939)
|
||||
- feat(core): experimental in-progress steering hints (1 of 3) by @joshualitt in
|
||||
[#19008](https://github.com/google-gemini/gemini-cli/pull/19008)
|
||||
- fix(patch): cherry-pick 261788c to release/v0.30.0-preview.0-pr-19453 to patch
|
||||
version v0.30.0-preview.0 and create version 0.30.0-preview.1 by
|
||||
@gemini-cli-robot in
|
||||
[#20568](https://github.com/google-gemini/gemini-cli/pull/20568)
|
||||
- fix(patch): cherry-pick ea48bd9 to release/v0.31.0-preview.1-pr-20577
|
||||
[CONFLICTS] by @gemini-cli-robot in
|
||||
[#20592](https://github.com/google-gemini/gemini-cli/pull/20592)
|
||||
- fix(patch): cherry-pick 32e777f to release/v0.31.0-preview.2-pr-20531 to patch
|
||||
version v0.31.0-preview.2 and create version 0.31.0-preview.3 by
|
||||
[#19490](https://github.com/google-gemini/gemini-cli/pull/19490)
|
||||
- fix(patch): cherry-pick c43500c to release/v0.30.0-preview.1-pr-19502 to patch
|
||||
version v0.30.0-preview.1 and create version 0.30.0-preview.2 by
|
||||
@gemini-cli-robot in
|
||||
[#20607](https://github.com/google-gemini/gemini-cli/pull/20607)
|
||||
[#19521](https://github.com/google-gemini/gemini-cli/pull/19521)
|
||||
- fix(patch): cherry-pick aa9163d to release/v0.30.0-preview.3-pr-19991 to patch
|
||||
version v0.30.0-preview.3 and create version 0.30.0-preview.4 by
|
||||
@gemini-cli-robot in
|
||||
[#20040](https://github.com/google-gemini/gemini-cli/pull/20040)
|
||||
- fix(patch): cherry-pick 2c1d6f8 to release/v0.30.0-preview.4-pr-19369 to patch
|
||||
version v0.30.0-preview.4 and create version 0.30.0-preview.5 by
|
||||
@gemini-cli-robot in
|
||||
[#20086](https://github.com/google-gemini/gemini-cli/pull/20086)
|
||||
- fix(patch): cherry-pick d96bd05 to release/v0.30.0-preview.5-pr-19867 to patch
|
||||
version v0.30.0-preview.5 and create version 0.30.0-preview.6 by
|
||||
@gemini-cli-robot in
|
||||
[#20112](https://github.com/google-gemini/gemini-cli/pull/20112)
|
||||
|
||||
**Full Changelog**:
|
||||
https://github.com/google-gemini/gemini-cli/compare/v0.30.1...v0.31.0
|
||||
https://github.com/google-gemini/gemini-cli/compare/v0.29.7...v0.30.1
|
||||
|
||||
+395
-187
@@ -1,4 +1,4 @@
|
||||
# Preview release: v0.32.0-preview.0
|
||||
# Preview release: v0.31.0-preview.1
|
||||
|
||||
Released: February 27, 2026
|
||||
|
||||
@@ -13,196 +13,404 @@ npm install -g @google/gemini-cli@preview
|
||||
|
||||
## Highlights
|
||||
|
||||
- **Plan Mode Enhancements**: Significant updates to Plan Mode, including
|
||||
support for modifying plans in external editors, adaptive workflows based on
|
||||
task complexity, and new integration tests.
|
||||
- **Agent and Core Engine Updates**: Enabled the generalist agent, introduced
|
||||
`Kind.Agent` for sub-agent classification, implemented task tracking
|
||||
foundation, and improved Agent-to-Agent (A2A) streaming and content
|
||||
extraction.
|
||||
- **CLI & User Experience**: Introduced interactive shell autocompletion, added
|
||||
a new verbosity mode for cleaner error reporting, enabled parallel loading of
|
||||
extensions, and improved UI hints and shortcut handling.
|
||||
- **Billing and Security**: Implemented G1 AI credits overage flow with enhanced
|
||||
billing telemetry, updated the authentication handshake to specification, and
|
||||
added support for a policy engine in extensions.
|
||||
- **Stability and Bug Fixes**: Addressed numerous issues including 100% CPU
|
||||
consumption by orphaned processes, enhanced retry logic for Code Assist,
|
||||
reduced intrusive MCP errors, and merged duplicate imports across packages.
|
||||
- **Plan Mode Enhancements**: Numerous additions including automatic model
|
||||
switching, custom storage directory configuration, message injection upon
|
||||
manual exit, enforcement of read-only constraints, and centralized tool
|
||||
visibility in the policy engine.
|
||||
- **Policy Engine Updates**: Project-level policy support added, alongside MCP
|
||||
server wildcard support, tool annotation propagation and matching, and
|
||||
workspace-level "Always Allow" persistence.
|
||||
- **MCP Integration Improvements**: Better integration through support for MCP
|
||||
progress updates with input validation and throttling, environment variable
|
||||
expansion for servers, and full details expansion on tool approval.
|
||||
- **CLI & Core UX Enhancements**: Several UI and quality-of-life updates such as
|
||||
Alt+D for forward word deletion, macOS run-event notifications, enhanced
|
||||
folder trust configurations with security warnings, improved startup warnings,
|
||||
and a new experimental browser agent.
|
||||
- **Security & Stability**: Introduced the Conseca framework, deceptive URL and
|
||||
Unicode character detection, stricter access checks, rate limits on web fetch,
|
||||
and resolved multiple dependency vulnerabilities.
|
||||
|
||||
## What's Changed
|
||||
|
||||
- feat(plan): add integration tests for plan mode by @Adib234 in
|
||||
[#20214](https://github.com/google-gemini/gemini-cli/pull/20214)
|
||||
- fix(acp): update auth handshake to spec by @skeshive in
|
||||
[#19725](https://github.com/google-gemini/gemini-cli/pull/19725)
|
||||
- feat(core): implement robust A2A streaming reassembly and fix task continuity
|
||||
by @adamfweidman in
|
||||
[#20091](https://github.com/google-gemini/gemini-cli/pull/20091)
|
||||
- feat(cli): load extensions in parallel by @scidomino in
|
||||
[#20229](https://github.com/google-gemini/gemini-cli/pull/20229)
|
||||
- Plumb the maxAttempts setting through Config args by @kevinjwang1 in
|
||||
[#20239](https://github.com/google-gemini/gemini-cli/pull/20239)
|
||||
- fix(cli): skip 404 errors in setup-github file downloads by @h30s in
|
||||
[#20287](https://github.com/google-gemini/gemini-cli/pull/20287)
|
||||
- fix(cli): expose model.name setting in settings dialog for persistence by
|
||||
@achaljhawar in
|
||||
[#19605](https://github.com/google-gemini/gemini-cli/pull/19605)
|
||||
- docs: remove legacy cmd examples in favor of powershell by @scidomino in
|
||||
[#20323](https://github.com/google-gemini/gemini-cli/pull/20323)
|
||||
- feat(core): Enable model steering in workspace. by @joshualitt in
|
||||
[#20343](https://github.com/google-gemini/gemini-cli/pull/20343)
|
||||
- fix: remove trailing comma in issue triage workflow settings json by @Nixxx19
|
||||
in [#20265](https://github.com/google-gemini/gemini-cli/pull/20265)
|
||||
- feat(core): implement task tracker foundation and service by @anj-s in
|
||||
[#19464](https://github.com/google-gemini/gemini-cli/pull/19464)
|
||||
- test: support tests that include color information by @jacob314 in
|
||||
[#20220](https://github.com/google-gemini/gemini-cli/pull/20220)
|
||||
- feat(core): introduce Kind.Agent for sub-agent classification by @abhipatel12
|
||||
in [#20369](https://github.com/google-gemini/gemini-cli/pull/20369)
|
||||
- Changelog for v0.30.0 by @gemini-cli-robot in
|
||||
[#20252](https://github.com/google-gemini/gemini-cli/pull/20252)
|
||||
- Update changelog workflow to reject nightly builds by @g-samroberts in
|
||||
[#20248](https://github.com/google-gemini/gemini-cli/pull/20248)
|
||||
- Changelog for v0.31.0-preview.0 by @gemini-cli-robot in
|
||||
[#20249](https://github.com/google-gemini/gemini-cli/pull/20249)
|
||||
- feat(cli): hide workspace policy update dialog and auto-accept by default by
|
||||
@Abhijit-2592 in
|
||||
[#20351](https://github.com/google-gemini/gemini-cli/pull/20351)
|
||||
- feat(core): rename grep_search include parameter to include_pattern by
|
||||
- fix(patch): cherry-pick 58df1c6 to release/v0.31.0-preview.0-pr-20374 to patch
|
||||
version v0.31.0-preview.0 and create version 0.31.0-preview.1 by
|
||||
@gemini-cli-robot in
|
||||
[#20568](https://github.com/google-gemini/gemini-cli/pull/20568)
|
||||
- Use ranged reads and limited searches and fuzzy editing improvements by
|
||||
@gundermanc in
|
||||
[#19240](https://github.com/google-gemini/gemini-cli/pull/19240)
|
||||
- Fix bottom border color by @jacob314 in
|
||||
[#19266](https://github.com/google-gemini/gemini-cli/pull/19266)
|
||||
- Release note generator fix by @g-samroberts in
|
||||
[#19363](https://github.com/google-gemini/gemini-cli/pull/19363)
|
||||
- test(evals): add behavioral tests for tool output masking by @NTaylorMullen in
|
||||
[#19172](https://github.com/google-gemini/gemini-cli/pull/19172)
|
||||
- docs: clarify preflight instructions in GEMINI.md by @NTaylorMullen in
|
||||
[#19377](https://github.com/google-gemini/gemini-cli/pull/19377)
|
||||
- feat(cli): add gemini --resume hint on exit by @Mag1ck in
|
||||
[#16285](https://github.com/google-gemini/gemini-cli/pull/16285)
|
||||
- fix: optimize height calculations for ask_user dialog by @jackwotherspoon in
|
||||
[#19017](https://github.com/google-gemini/gemini-cli/pull/19017)
|
||||
- feat(cli): add Alt+D for forward word deletion by @scidomino in
|
||||
[#19300](https://github.com/google-gemini/gemini-cli/pull/19300)
|
||||
- Disable failing eval test by @chrstnb in
|
||||
[#19455](https://github.com/google-gemini/gemini-cli/pull/19455)
|
||||
- fix(cli): support legacy onConfirm callback in ToolActionsContext by
|
||||
@SandyTao520 in
|
||||
[#20328](https://github.com/google-gemini/gemini-cli/pull/20328)
|
||||
- feat(plan): support opening and modifying plan in external editor by @Adib234
|
||||
in [#20348](https://github.com/google-gemini/gemini-cli/pull/20348)
|
||||
- feat(cli): implement interactive shell autocompletion by @mrpmohiburrahman in
|
||||
[#20082](https://github.com/google-gemini/gemini-cli/pull/20082)
|
||||
- fix(core): allow /memory add to work in plan mode by @Jefftree in
|
||||
[#20353](https://github.com/google-gemini/gemini-cli/pull/20353)
|
||||
- feat(core): add HTTP 499 to retryable errors and map to RetryableQuotaError by
|
||||
@bdmorgan in [#20432](https://github.com/google-gemini/gemini-cli/pull/20432)
|
||||
- feat(core): Enable generalist agent by @joshualitt in
|
||||
[#19665](https://github.com/google-gemini/gemini-cli/pull/19665)
|
||||
- Updated tests in TableRenderer.test.tsx to use SVG snapshots by @devr0306 in
|
||||
[#20450](https://github.com/google-gemini/gemini-cli/pull/20450)
|
||||
- Refactor Github Action per b/485167538 by @google-admin in
|
||||
[#19443](https://github.com/google-gemini/gemini-cli/pull/19443)
|
||||
- fix(github): resolve actionlint and yamllint regressions from #19443 by @jerop
|
||||
in [#20467](https://github.com/google-gemini/gemini-cli/pull/20467)
|
||||
- fix: action var usage by @galz10 in
|
||||
[#20492](https://github.com/google-gemini/gemini-cli/pull/20492)
|
||||
- feat(core): improve A2A content extraction by @adamfweidman in
|
||||
[#20487](https://github.com/google-gemini/gemini-cli/pull/20487)
|
||||
- fix(cli): support quota error fallbacks for all authentication types by
|
||||
@sehoon38 in [#20475](https://github.com/google-gemini/gemini-cli/pull/20475)
|
||||
- fix(core): flush transcript for pure tool-call responses to ensure BeforeTool
|
||||
hooks see complete state by @krishdef7 in
|
||||
[#20419](https://github.com/google-gemini/gemini-cli/pull/20419)
|
||||
- feat(plan): adapt planning workflow based on complexity of task by @jerop in
|
||||
[#20465](https://github.com/google-gemini/gemini-cli/pull/20465)
|
||||
- fix: prevent orphaned processes from consuming 100% CPU when terminal closes
|
||||
by @yuvrajangadsingh in
|
||||
[#16965](https://github.com/google-gemini/gemini-cli/pull/16965)
|
||||
- feat(core): increase fetch timeout and fix [object Object] error
|
||||
stringification by @bdmorgan in
|
||||
[#20441](https://github.com/google-gemini/gemini-cli/pull/20441)
|
||||
- [Gemma x Gemini CLI] Add an Experimental Gemma Router that uses a LiteRT-LM
|
||||
shim into the Composite Model Classifier Strategy by @sidwan02 in
|
||||
[#17231](https://github.com/google-gemini/gemini-cli/pull/17231)
|
||||
- docs(plan): update documentation regarding supporting editing of plan files
|
||||
during plan approval by @Adib234 in
|
||||
[#20452](https://github.com/google-gemini/gemini-cli/pull/20452)
|
||||
- test(cli): fix flaky ToolResultDisplay overflow test by @jwhelangoog in
|
||||
[#20518](https://github.com/google-gemini/gemini-cli/pull/20518)
|
||||
- ui(cli): reduce length of Ctrl+O hint by @jwhelangoog in
|
||||
[#20490](https://github.com/google-gemini/gemini-cli/pull/20490)
|
||||
- fix(ui): correct styled table width calculations by @devr0306 in
|
||||
[#20042](https://github.com/google-gemini/gemini-cli/pull/20042)
|
||||
- Avoid overaggressive unescaping by @scidomino in
|
||||
[#20520](https://github.com/google-gemini/gemini-cli/pull/20520)
|
||||
- feat(telemetry) Instrument traces with more attributes and make them available
|
||||
to OTEL users by @heaventourist in
|
||||
[#20237](https://github.com/google-gemini/gemini-cli/pull/20237)
|
||||
- Add support for policy engine in extensions by @chrstnb in
|
||||
[#20049](https://github.com/google-gemini/gemini-cli/pull/20049)
|
||||
- Docs: Update to Terms of Service & FAQ by @jkcinouye in
|
||||
[#20488](https://github.com/google-gemini/gemini-cli/pull/20488)
|
||||
- Fix bottom border rendering for search and add a regression test. by @jacob314
|
||||
in [#20517](https://github.com/google-gemini/gemini-cli/pull/20517)
|
||||
- fix(core): apply retry logic to CodeAssistServer for all users by @bdmorgan in
|
||||
[#20507](https://github.com/google-gemini/gemini-cli/pull/20507)
|
||||
- Fix extension MCP server env var loading by @chrstnb in
|
||||
[#20374](https://github.com/google-gemini/gemini-cli/pull/20374)
|
||||
- feat(ui): add 'ctrl+o' hint to truncated content message by @jerop in
|
||||
[#20529](https://github.com/google-gemini/gemini-cli/pull/20529)
|
||||
- Fix flicker showing message to press ctrl-O again to collapse. by @jacob314 in
|
||||
[#20414](https://github.com/google-gemini/gemini-cli/pull/20414)
|
||||
- fix(cli): hide shortcuts hint while model is thinking or the user has typed a
|
||||
prompt + add debounce to avoid flicker by @jacob314 in
|
||||
[#19389](https://github.com/google-gemini/gemini-cli/pull/19389)
|
||||
- feat(plan): update planning workflow to encourage multi-select with
|
||||
descriptions of options by @Adib234 in
|
||||
[#20491](https://github.com/google-gemini/gemini-cli/pull/20491)
|
||||
- refactor(core,cli): useAlternateBuffer read from config by @psinha40898 in
|
||||
[#20346](https://github.com/google-gemini/gemini-cli/pull/20346)
|
||||
- fix(cli): ensure dialogs stay scrolled to bottom in alternate buffer mode by
|
||||
@jacob314 in [#20527](https://github.com/google-gemini/gemini-cli/pull/20527)
|
||||
- fix(core): revert auto-save of policies to user space by @Abhijit-2592 in
|
||||
[#20531](https://github.com/google-gemini/gemini-cli/pull/20531)
|
||||
- Demote unreliable test. by @gundermanc in
|
||||
[#20571](https://github.com/google-gemini/gemini-cli/pull/20571)
|
||||
- fix(core): handle optional response fields from code assist API by @sehoon38
|
||||
in [#20345](https://github.com/google-gemini/gemini-cli/pull/20345)
|
||||
- fix(cli): keep thought summary when loading phrases are off by @LyalinDotCom
|
||||
in [#20497](https://github.com/google-gemini/gemini-cli/pull/20497)
|
||||
- feat(cli): add temporary flag to disable workspace policies by @Abhijit-2592
|
||||
in [#20523](https://github.com/google-gemini/gemini-cli/pull/20523)
|
||||
- Disable expensive and scheduled workflows on personal forks by @dewitt in
|
||||
[#20449](https://github.com/google-gemini/gemini-cli/pull/20449)
|
||||
- Moved markdown parsing logic to a separate util file by @devr0306 in
|
||||
[#20526](https://github.com/google-gemini/gemini-cli/pull/20526)
|
||||
- fix(plan): prevent agent from using ask_user for shell command confirmation by
|
||||
@Adib234 in [#20504](https://github.com/google-gemini/gemini-cli/pull/20504)
|
||||
- fix(core): disable retries for code assist streaming requests by @sehoon38 in
|
||||
[#20561](https://github.com/google-gemini/gemini-cli/pull/20561)
|
||||
- feat(billing): implement G1 AI credits overage flow with billing telemetry by
|
||||
@gsquared94 in
|
||||
[#18590](https://github.com/google-gemini/gemini-cli/pull/18590)
|
||||
- feat: better error messages by @gsquared94 in
|
||||
[#20577](https://github.com/google-gemini/gemini-cli/pull/20577)
|
||||
- fix(ui): persist expansion in AskUser dialog when navigating options by @jerop
|
||||
in [#20559](https://github.com/google-gemini/gemini-cli/pull/20559)
|
||||
- fix(cli): prevent sub-agent tool calls from leaking into UI by @abhipatel12 in
|
||||
[#20580](https://github.com/google-gemini/gemini-cli/pull/20580)
|
||||
- fix(cli): Shell autocomplete polish by @jacob314 in
|
||||
[#20411](https://github.com/google-gemini/gemini-cli/pull/20411)
|
||||
- Changelog for v0.31.0-preview.1 by @gemini-cli-robot in
|
||||
[#20590](https://github.com/google-gemini/gemini-cli/pull/20590)
|
||||
- Add slash command for promoting behavioral evals to CI blocking by @gundermanc
|
||||
in [#20575](https://github.com/google-gemini/gemini-cli/pull/20575)
|
||||
- Changelog for v0.30.1 by @gemini-cli-robot in
|
||||
[#20589](https://github.com/google-gemini/gemini-cli/pull/20589)
|
||||
- Add low/full CLI error verbosity mode for cleaner UI by @LyalinDotCom in
|
||||
[#20399](https://github.com/google-gemini/gemini-cli/pull/20399)
|
||||
- Disable Gemini PR reviews on draft PRs. by @gundermanc in
|
||||
[#20362](https://github.com/google-gemini/gemini-cli/pull/20362)
|
||||
- Docs: FAQ update by @jkcinouye in
|
||||
[#20585](https://github.com/google-gemini/gemini-cli/pull/20585)
|
||||
- fix(core): reduce intrusive MCP errors and deduplicate diagnostics by
|
||||
[#19369](https://github.com/google-gemini/gemini-cli/pull/19369)
|
||||
- chore(deps): bump tar from 7.5.7 to 7.5.8 by dependabot[bot] in
|
||||
[#19367](https://github.com/google-gemini/gemini-cli/pull/19367)
|
||||
- fix(plan): allow safe fallback when experiment setting for plan is not enabled
|
||||
but approval mode at startup is plan by @Adib234 in
|
||||
[#19439](https://github.com/google-gemini/gemini-cli/pull/19439)
|
||||
- Add explicit color-convert dependency by @chrstnb in
|
||||
[#19460](https://github.com/google-gemini/gemini-cli/pull/19460)
|
||||
- feat(devtools): migrate devtools package into monorepo by @SandyTao520 in
|
||||
[#18936](https://github.com/google-gemini/gemini-cli/pull/18936)
|
||||
- fix(core): clarify plan mode constraints and exit mechanism by @jerop in
|
||||
[#19438](https://github.com/google-gemini/gemini-cli/pull/19438)
|
||||
- feat(cli): add macOS run-event notifications (interactive only) by
|
||||
@LyalinDotCom in
|
||||
[#19056](https://github.com/google-gemini/gemini-cli/pull/19056)
|
||||
- Changelog for v0.29.0 by @gemini-cli-robot in
|
||||
[#19361](https://github.com/google-gemini/gemini-cli/pull/19361)
|
||||
- fix(ui): preventing empty history items from being added by @devr0306 in
|
||||
[#19014](https://github.com/google-gemini/gemini-cli/pull/19014)
|
||||
- Changelog for v0.30.0-preview.0 by @gemini-cli-robot in
|
||||
[#19364](https://github.com/google-gemini/gemini-cli/pull/19364)
|
||||
- feat(core): add support for MCP progress updates by @NTaylorMullen in
|
||||
[#19046](https://github.com/google-gemini/gemini-cli/pull/19046)
|
||||
- fix(core): ensure directory exists before writing conversation file by
|
||||
@godwiniheuwa in
|
||||
[#18429](https://github.com/google-gemini/gemini-cli/pull/18429)
|
||||
- fix(ui): move margin from top to bottom in ToolGroupMessage by @imadraude in
|
||||
[#17198](https://github.com/google-gemini/gemini-cli/pull/17198)
|
||||
- fix(cli): treat unknown slash commands as regular input instead of showing
|
||||
error by @skyvanguard in
|
||||
[#17393](https://github.com/google-gemini/gemini-cli/pull/17393)
|
||||
- feat(core): experimental in-progress steering hints (2 of 2) by @joshualitt in
|
||||
[#19307](https://github.com/google-gemini/gemini-cli/pull/19307)
|
||||
- docs(plan): add documentation for plan mode command by @Adib234 in
|
||||
[#19467](https://github.com/google-gemini/gemini-cli/pull/19467)
|
||||
- fix(core): ripgrep fails when pattern looks like ripgrep flag by @syvb in
|
||||
[#18858](https://github.com/google-gemini/gemini-cli/pull/18858)
|
||||
- fix(cli): disable auto-completion on Shift+Tab to preserve mode cycling by
|
||||
@NTaylorMullen in
|
||||
[#19451](https://github.com/google-gemini/gemini-cli/pull/19451)
|
||||
- use issuer instead of authorization_endpoint for oauth discovery by
|
||||
@garrettsparks in
|
||||
[#17332](https://github.com/google-gemini/gemini-cli/pull/17332)
|
||||
- feat(cli): include `/dir add` directories in @ autocomplete suggestions by
|
||||
@jasmeetsb in [#19246](https://github.com/google-gemini/gemini-cli/pull/19246)
|
||||
- feat(admin): Admin settings should only apply if adminControlsApplicable =
|
||||
true and fetch errors should be fatal by @skeshive in
|
||||
[#19453](https://github.com/google-gemini/gemini-cli/pull/19453)
|
||||
- Format strict-development-rules command by @g-samroberts in
|
||||
[#19484](https://github.com/google-gemini/gemini-cli/pull/19484)
|
||||
- feat(core): centralize compatibility checks and add TrueColor detection by
|
||||
@spencer426 in
|
||||
[#20232](https://github.com/google-gemini/gemini-cli/pull/20232)
|
||||
- docs: fix spelling typos in installation guide by @campox747 in
|
||||
[#20579](https://github.com/google-gemini/gemini-cli/pull/20579)
|
||||
- Promote stable tests to CI blocking. by @gundermanc in
|
||||
[#20581](https://github.com/google-gemini/gemini-cli/pull/20581)
|
||||
- feat(core): enable contiguous parallel admission for Kind.Agent tools by
|
||||
@abhipatel12 in
|
||||
[#20583](https://github.com/google-gemini/gemini-cli/pull/20583)
|
||||
- Enforce import/no-duplicates as error by @Nixxx19 in
|
||||
[#19797](https://github.com/google-gemini/gemini-cli/pull/19797)
|
||||
- fix: merge duplicate imports in sdk and test-utils packages (1/4) by @Nixxx19
|
||||
in [#19777](https://github.com/google-gemini/gemini-cli/pull/19777)
|
||||
- fix: merge duplicate imports in a2a-server package (2/4) by @Nixxx19 in
|
||||
[#19781](https://github.com/google-gemini/gemini-cli/pull/19781)
|
||||
[#19478](https://github.com/google-gemini/gemini-cli/pull/19478)
|
||||
- Remove unused files and update index and sidebar. by @g-samroberts in
|
||||
[#19479](https://github.com/google-gemini/gemini-cli/pull/19479)
|
||||
- Migrate core render util to use xterm.js as part of the rendering loop. by
|
||||
@jacob314 in [#19044](https://github.com/google-gemini/gemini-cli/pull/19044)
|
||||
- Changelog for v0.30.0-preview.1 by @gemini-cli-robot in
|
||||
[#19496](https://github.com/google-gemini/gemini-cli/pull/19496)
|
||||
- build: replace deprecated built-in punycode with userland package by @jacob314
|
||||
in [#19502](https://github.com/google-gemini/gemini-cli/pull/19502)
|
||||
- Speculative fixes to try to fix react error. by @jacob314 in
|
||||
[#19508](https://github.com/google-gemini/gemini-cli/pull/19508)
|
||||
- fix spacing by @jacob314 in
|
||||
[#19494](https://github.com/google-gemini/gemini-cli/pull/19494)
|
||||
- fix(core): ensure user rejections update tool outcome for telemetry by
|
||||
@abhiasap in [#18982](https://github.com/google-gemini/gemini-cli/pull/18982)
|
||||
- fix(acp): Initialize config (#18897) by @Mervap in
|
||||
[#18898](https://github.com/google-gemini/gemini-cli/pull/18898)
|
||||
- fix(core): add error logging for IDE fetch failures by @yuvrajangadsingh in
|
||||
[#17981](https://github.com/google-gemini/gemini-cli/pull/17981)
|
||||
- feat(acp): support set_mode interface (#18890) by @Mervap in
|
||||
[#18891](https://github.com/google-gemini/gemini-cli/pull/18891)
|
||||
- fix(core): robust workspace-based IDE connection discovery by @ehedlund in
|
||||
[#18443](https://github.com/google-gemini/gemini-cli/pull/18443)
|
||||
- Deflake windows tests. by @jacob314 in
|
||||
[#19511](https://github.com/google-gemini/gemini-cli/pull/19511)
|
||||
- Fix: Avoid tool confirmation timeout when no UI listeners are present by
|
||||
@pdHaku0 in [#17955](https://github.com/google-gemini/gemini-cli/pull/17955)
|
||||
- format md file by @scidomino in
|
||||
[#19474](https://github.com/google-gemini/gemini-cli/pull/19474)
|
||||
- feat(cli): add experimental.useOSC52Copy setting by @scidomino in
|
||||
[#19488](https://github.com/google-gemini/gemini-cli/pull/19488)
|
||||
- feat(cli): replace loading phrases boolean with enum setting by @LyalinDotCom
|
||||
in [#19347](https://github.com/google-gemini/gemini-cli/pull/19347)
|
||||
- Update skill to adjust for generated results. by @g-samroberts in
|
||||
[#19500](https://github.com/google-gemini/gemini-cli/pull/19500)
|
||||
- Fix message too large issue. by @gundermanc in
|
||||
[#19499](https://github.com/google-gemini/gemini-cli/pull/19499)
|
||||
- fix(core): prevent duplicate tool approval entries in auto-saved.toml by
|
||||
@Abhijit-2592 in
|
||||
[#19487](https://github.com/google-gemini/gemini-cli/pull/19487)
|
||||
- fix(core): resolve crash in ClearcutLogger when os.cpus() is empty by @Adib234
|
||||
in [#19555](https://github.com/google-gemini/gemini-cli/pull/19555)
|
||||
- chore(core): improve encapsulation and remove unused exports by @adamfweidman
|
||||
in [#19556](https://github.com/google-gemini/gemini-cli/pull/19556)
|
||||
- Revert "Add generic searchable list to back settings and extensions (… by
|
||||
@chrstnb in [#19434](https://github.com/google-gemini/gemini-cli/pull/19434)
|
||||
- fix(core): improve error type extraction for telemetry by @yunaseoul in
|
||||
[#19565](https://github.com/google-gemini/gemini-cli/pull/19565)
|
||||
- fix: remove extra padding in Composer by @jackwotherspoon in
|
||||
[#19529](https://github.com/google-gemini/gemini-cli/pull/19529)
|
||||
- feat(plan): support configuring custom plans storage directory by @jerop in
|
||||
[#19577](https://github.com/google-gemini/gemini-cli/pull/19577)
|
||||
- Migrate files to resource or references folder. by @g-samroberts in
|
||||
[#19503](https://github.com/google-gemini/gemini-cli/pull/19503)
|
||||
- feat(policy): implement project-level policy support by @Abhijit-2592 in
|
||||
[#18682](https://github.com/google-gemini/gemini-cli/pull/18682)
|
||||
- feat(core): Implement parallel FC for read only tools. by @joshualitt in
|
||||
[#18791](https://github.com/google-gemini/gemini-cli/pull/18791)
|
||||
- chore(skills): adds pr-address-comments skill to work on PR feedback by
|
||||
@mbleigh in [#19576](https://github.com/google-gemini/gemini-cli/pull/19576)
|
||||
- refactor(sdk): introduce session-based architecture by @mbleigh in
|
||||
[#19180](https://github.com/google-gemini/gemini-cli/pull/19180)
|
||||
- fix(ci): add fallback JSON extraction to issue triage workflow by @bdmorgan in
|
||||
[#19593](https://github.com/google-gemini/gemini-cli/pull/19593)
|
||||
- feat(core): refine Edit and WriteFile tool schemas for Gemini 3 by
|
||||
@SandyTao520 in
|
||||
[#19476](https://github.com/google-gemini/gemini-cli/pull/19476)
|
||||
- Changelog for v0.30.0-preview.3 by @gemini-cli-robot in
|
||||
[#19585](https://github.com/google-gemini/gemini-cli/pull/19585)
|
||||
- fix(plan): exclude EnterPlanMode tool from YOLO mode by @Adib234 in
|
||||
[#19570](https://github.com/google-gemini/gemini-cli/pull/19570)
|
||||
- chore: resolve build warnings and update dependencies by @mattKorwel in
|
||||
[#18880](https://github.com/google-gemini/gemini-cli/pull/18880)
|
||||
- feat(ui): add source indicators to slash commands by @ehedlund in
|
||||
[#18839](https://github.com/google-gemini/gemini-cli/pull/18839)
|
||||
- docs: refine Plan Mode documentation structure and workflow by @jerop in
|
||||
[#19644](https://github.com/google-gemini/gemini-cli/pull/19644)
|
||||
- Docs: Update release information regarding Gemini 3.1 by @jkcinouye in
|
||||
[#19568](https://github.com/google-gemini/gemini-cli/pull/19568)
|
||||
- fix(security): rate limit web_fetch tool to mitigate DDoS via prompt injection
|
||||
by @mattKorwel in
|
||||
[#19567](https://github.com/google-gemini/gemini-cli/pull/19567)
|
||||
- Add initial implementation of /extensions explore command by @chrstnb in
|
||||
[#19029](https://github.com/google-gemini/gemini-cli/pull/19029)
|
||||
- fix: use discoverOAuthFromWWWAuthenticate for reactive OAuth flow (#18760) by
|
||||
@maximus12793 in
|
||||
[#19038](https://github.com/google-gemini/gemini-cli/pull/19038)
|
||||
- Search updates by @alisa-alisa in
|
||||
[#19482](https://github.com/google-gemini/gemini-cli/pull/19482)
|
||||
- feat(cli): add support for numpad SS3 sequences by @scidomino in
|
||||
[#19659](https://github.com/google-gemini/gemini-cli/pull/19659)
|
||||
- feat(cli): enhance folder trust with configuration discovery and security
|
||||
warnings by @galz10 in
|
||||
[#19492](https://github.com/google-gemini/gemini-cli/pull/19492)
|
||||
- feat(ui): improve startup warnings UX with dismissal and show-count limits by
|
||||
@spencer426 in
|
||||
[#19584](https://github.com/google-gemini/gemini-cli/pull/19584)
|
||||
- feat(a2a): Add API key authentication provider by @adamfweidman in
|
||||
[#19548](https://github.com/google-gemini/gemini-cli/pull/19548)
|
||||
- Send accepted/removed lines with ACCEPT_FILE telemetry. by @gundermanc in
|
||||
[#19670](https://github.com/google-gemini/gemini-cli/pull/19670)
|
||||
- feat(models): support Gemini 3.1 Pro Preview and fixes by @sehoon38 in
|
||||
[#19676](https://github.com/google-gemini/gemini-cli/pull/19676)
|
||||
- feat(plan): enforce read-only constraints in Plan Mode by @mattKorwel in
|
||||
[#19433](https://github.com/google-gemini/gemini-cli/pull/19433)
|
||||
- fix(cli): allow perfect match @scripts/test-windows-paths.js completions to
|
||||
submit on Enter by @spencer426 in
|
||||
[#19562](https://github.com/google-gemini/gemini-cli/pull/19562)
|
||||
- fix(core): treat 503 Service Unavailable as retryable quota error by @sehoon38
|
||||
in [#19642](https://github.com/google-gemini/gemini-cli/pull/19642)
|
||||
- Update sidebar.json for to allow top nav tabs. by @g-samroberts in
|
||||
[#19595](https://github.com/google-gemini/gemini-cli/pull/19595)
|
||||
- security: strip deceptive Unicode characters from terminal output by @ehedlund
|
||||
in [#19026](https://github.com/google-gemini/gemini-cli/pull/19026)
|
||||
- Fixes 'input.on' is not a function error in Gemini CLI by @gundermanc in
|
||||
[#19691](https://github.com/google-gemini/gemini-cli/pull/19691)
|
||||
- Revert "feat(ui): add source indicators to slash commands" by @ehedlund in
|
||||
[#19695](https://github.com/google-gemini/gemini-cli/pull/19695)
|
||||
- security: implement deceptive URL detection and disclosure in tool
|
||||
confirmations by @ehedlund in
|
||||
[#19288](https://github.com/google-gemini/gemini-cli/pull/19288)
|
||||
- fix(core): restore auth consent in headless mode and add unit tests by
|
||||
@ehedlund in [#19689](https://github.com/google-gemini/gemini-cli/pull/19689)
|
||||
- Fix unsafe assertions in code_assist folder. by @gundermanc in
|
||||
[#19706](https://github.com/google-gemini/gemini-cli/pull/19706)
|
||||
- feat(cli): make JetBrains warning more specific by @jacob314 in
|
||||
[#19687](https://github.com/google-gemini/gemini-cli/pull/19687)
|
||||
- fix(cli): extensions dialog UX polish by @jacob314 in
|
||||
[#19685](https://github.com/google-gemini/gemini-cli/pull/19685)
|
||||
- fix(cli): use getDisplayString for manual model selection in dialog by
|
||||
@sehoon38 in [#19726](https://github.com/google-gemini/gemini-cli/pull/19726)
|
||||
- feat(policy): repurpose "Always Allow" persistence to workspace level by
|
||||
@Abhijit-2592 in
|
||||
[#19707](https://github.com/google-gemini/gemini-cli/pull/19707)
|
||||
- fix(cli): re-enable CLI banner by @sehoon38 in
|
||||
[#19741](https://github.com/google-gemini/gemini-cli/pull/19741)
|
||||
- Disallow and suppress unsafe assignment by @gundermanc in
|
||||
[#19736](https://github.com/google-gemini/gemini-cli/pull/19736)
|
||||
- feat(core): migrate read_file to 1-based start_line/end_line parameters by
|
||||
@adamfweidman in
|
||||
[#19526](https://github.com/google-gemini/gemini-cli/pull/19526)
|
||||
- feat(cli): improve CTRL+O experience for both standard and alternate screen
|
||||
buffer (ASB) modes by @jwhelangoog in
|
||||
[#19010](https://github.com/google-gemini/gemini-cli/pull/19010)
|
||||
- Utilize pipelining of grep_search -> read_file to eliminate turns by
|
||||
@gundermanc in
|
||||
[#19574](https://github.com/google-gemini/gemini-cli/pull/19574)
|
||||
- refactor(core): remove unsafe type assertions in error utils (Phase 1.1) by
|
||||
@mattKorwel in
|
||||
[#19750](https://github.com/google-gemini/gemini-cli/pull/19750)
|
||||
- Disallow unsafe returns. by @gundermanc in
|
||||
[#19767](https://github.com/google-gemini/gemini-cli/pull/19767)
|
||||
- fix(cli): filter subagent sessions from resume history by @abhipatel12 in
|
||||
[#19698](https://github.com/google-gemini/gemini-cli/pull/19698)
|
||||
- chore(lint): fix lint errors seen when running npm run lint by @abhipatel12 in
|
||||
[#19844](https://github.com/google-gemini/gemini-cli/pull/19844)
|
||||
- feat(core): remove unnecessary login verbiage from Code Assist auth by
|
||||
@NTaylorMullen in
|
||||
[#19861](https://github.com/google-gemini/gemini-cli/pull/19861)
|
||||
- fix(plan): time share by approval mode dashboard reporting negative time
|
||||
shares by @Adib234 in
|
||||
[#19847](https://github.com/google-gemini/gemini-cli/pull/19847)
|
||||
- fix(core): allow any preview model in quota access check by @bdmorgan in
|
||||
[#19867](https://github.com/google-gemini/gemini-cli/pull/19867)
|
||||
- fix(core): prevent omission placeholder deletions in replace/write_file by
|
||||
@nsalerni in [#19870](https://github.com/google-gemini/gemini-cli/pull/19870)
|
||||
- fix(core): add uniqueness guard to edit tool by @Shivangisharma4 in
|
||||
[#19890](https://github.com/google-gemini/gemini-cli/pull/19890)
|
||||
- refactor(config): remove enablePromptCompletion from settings by @sehoon38 in
|
||||
[#19974](https://github.com/google-gemini/gemini-cli/pull/19974)
|
||||
- refactor(core): move session conversion logic to core by @abhipatel12 in
|
||||
[#19972](https://github.com/google-gemini/gemini-cli/pull/19972)
|
||||
- Fix: Persist manual model selection on restart #19864 by @Nixxx19 in
|
||||
[#19891](https://github.com/google-gemini/gemini-cli/pull/19891)
|
||||
- fix(core): increase default retry attempts and add quota error backoff by
|
||||
@sehoon38 in [#19949](https://github.com/google-gemini/gemini-cli/pull/19949)
|
||||
- feat(core): add policy chain support for Gemini 3.1 by @sehoon38 in
|
||||
[#19991](https://github.com/google-gemini/gemini-cli/pull/19991)
|
||||
- Updates command reference and /stats command. by @g-samroberts in
|
||||
[#19794](https://github.com/google-gemini/gemini-cli/pull/19794)
|
||||
- Fix for silent failures in non-interactive mode by @owenofbrien in
|
||||
[#19905](https://github.com/google-gemini/gemini-cli/pull/19905)
|
||||
- fix(plan): allow plan mode writes on Windows and fix prompt paths by @Adib234
|
||||
in [#19658](https://github.com/google-gemini/gemini-cli/pull/19658)
|
||||
- fix(core): prevent OAuth server crash on unexpected requests by @reyyanxahmed
|
||||
in [#19668](https://github.com/google-gemini/gemini-cli/pull/19668)
|
||||
- feat: Map tool kinds to explicit ACP.ToolKind values and update test … by
|
||||
@sripasg in [#19547](https://github.com/google-gemini/gemini-cli/pull/19547)
|
||||
- chore: restrict gemini-automted-issue-triage to only allow echo by @galz10 in
|
||||
[#20047](https://github.com/google-gemini/gemini-cli/pull/20047)
|
||||
- Allow ask headers longer than 16 chars by @scidomino in
|
||||
[#20041](https://github.com/google-gemini/gemini-cli/pull/20041)
|
||||
- fix(core): prevent state corruption in McpClientManager during collis by @h30s
|
||||
in [#19782](https://github.com/google-gemini/gemini-cli/pull/19782)
|
||||
- fix(bundling): copy devtools package to bundle for runtime resolution by
|
||||
@SandyTao520 in
|
||||
[#19766](https://github.com/google-gemini/gemini-cli/pull/19766)
|
||||
- feat(policy): Support MCP Server Wildcards in Policy Engine by @jerop in
|
||||
[#20024](https://github.com/google-gemini/gemini-cli/pull/20024)
|
||||
- docs(CONTRIBUTING): update React DevTools version to 6 by @mmgok in
|
||||
[#20014](https://github.com/google-gemini/gemini-cli/pull/20014)
|
||||
- feat(core): optimize tool descriptions and schemas for Gemini 3 by
|
||||
@aishaneeshah in
|
||||
[#19643](https://github.com/google-gemini/gemini-cli/pull/19643)
|
||||
- feat(core): implement experimental direct web fetch by @mbleigh in
|
||||
[#19557](https://github.com/google-gemini/gemini-cli/pull/19557)
|
||||
- feat(core): replace expected_replacements with allow_multiple in replace tool
|
||||
by @SandyTao520 in
|
||||
[#20033](https://github.com/google-gemini/gemini-cli/pull/20033)
|
||||
- fix(sandbox): harden image packaging integrity checks by @aviralgarg05 in
|
||||
[#19552](https://github.com/google-gemini/gemini-cli/pull/19552)
|
||||
- fix(core): allow environment variable expansion and explicit overrides for MCP
|
||||
servers by @galz10 in
|
||||
[#18837](https://github.com/google-gemini/gemini-cli/pull/18837)
|
||||
- feat(policy): Implement Tool Annotation Matching in Policy Engine by @jerop in
|
||||
[#20029](https://github.com/google-gemini/gemini-cli/pull/20029)
|
||||
- fix(core): prevent utility calls from changing session active model by
|
||||
@adamfweidman in
|
||||
[#20035](https://github.com/google-gemini/gemini-cli/pull/20035)
|
||||
- fix(cli): skip workspace policy loading when in home directory by
|
||||
@Abhijit-2592 in
|
||||
[#20054](https://github.com/google-gemini/gemini-cli/pull/20054)
|
||||
- fix(scripts): Add Windows (win32/x64) support to lint.js by @ZafeerMahmood in
|
||||
[#16193](https://github.com/google-gemini/gemini-cli/pull/16193)
|
||||
- fix(a2a-server): Remove unsafe type assertions in agent by @Nixxx19 in
|
||||
[#19723](https://github.com/google-gemini/gemini-cli/pull/19723)
|
||||
- Fix: Handle corrupted token file gracefully when switching auth types (#19845)
|
||||
by @Nixxx19 in
|
||||
[#19850](https://github.com/google-gemini/gemini-cli/pull/19850)
|
||||
- fix critical dep vulnerability by @scidomino in
|
||||
[#20087](https://github.com/google-gemini/gemini-cli/pull/20087)
|
||||
- Add new setting to configure maxRetries by @kevinjwang1 in
|
||||
[#20064](https://github.com/google-gemini/gemini-cli/pull/20064)
|
||||
- Stabilize tests. by @gundermanc in
|
||||
[#20095](https://github.com/google-gemini/gemini-cli/pull/20095)
|
||||
- make windows tests mandatory by @scidomino in
|
||||
[#20096](https://github.com/google-gemini/gemini-cli/pull/20096)
|
||||
- Add 3.1 pro preview to behavioral evals. by @gundermanc in
|
||||
[#20088](https://github.com/google-gemini/gemini-cli/pull/20088)
|
||||
- feat:PR-rate-limit by @JagjeevanAK in
|
||||
[#19804](https://github.com/google-gemini/gemini-cli/pull/19804)
|
||||
- feat(cli): allow expanding full details of MCP tool on approval by @y-okt in
|
||||
[#19916](https://github.com/google-gemini/gemini-cli/pull/19916)
|
||||
- feat(security): Introduce Conseca framework by @shrishabh in
|
||||
[#13193](https://github.com/google-gemini/gemini-cli/pull/13193)
|
||||
- fix(cli): Remove unsafe type assertions in activityLogger #19713 by @Nixxx19
|
||||
in [#19745](https://github.com/google-gemini/gemini-cli/pull/19745)
|
||||
- feat: implement AfterTool tail tool calls by @googlestrobe in
|
||||
[#18486](https://github.com/google-gemini/gemini-cli/pull/18486)
|
||||
- ci(actions): fix PR rate limiter excluding maintainers by @scidomino in
|
||||
[#20117](https://github.com/google-gemini/gemini-cli/pull/20117)
|
||||
- Shortcuts: Move SectionHeader title below top line and refine styling by
|
||||
@keithguerin in
|
||||
[#18721](https://github.com/google-gemini/gemini-cli/pull/18721)
|
||||
- refactor(ui): Update and simplify use of gray colors in themes by @keithguerin
|
||||
in [#20141](https://github.com/google-gemini/gemini-cli/pull/20141)
|
||||
- fix punycode2 by @jacob314 in
|
||||
[#20154](https://github.com/google-gemini/gemini-cli/pull/20154)
|
||||
- feat(ide): add GEMINI_CLI_IDE_PID env var to override IDE process detection by
|
||||
@kiryltech in [#15842](https://github.com/google-gemini/gemini-cli/pull/15842)
|
||||
- feat(policy): Propagate Tool Annotations for MCP Servers by @jerop in
|
||||
[#20083](https://github.com/google-gemini/gemini-cli/pull/20083)
|
||||
- fix(a2a-server): pass allowedTools settings to core Config by @reyyanxahmed in
|
||||
[#19680](https://github.com/google-gemini/gemini-cli/pull/19680)
|
||||
- feat(mcp): add progress bar, throttling, and input validation for MCP tool
|
||||
progress by @jasmeetsb in
|
||||
[#19772](https://github.com/google-gemini/gemini-cli/pull/19772)
|
||||
- feat(policy): centralize plan mode tool visibility in policy engine by @jerop
|
||||
in [#20178](https://github.com/google-gemini/gemini-cli/pull/20178)
|
||||
- feat(browser): implement experimental browser agent by @gsquared94 in
|
||||
[#19284](https://github.com/google-gemini/gemini-cli/pull/19284)
|
||||
- feat(plan): summarize work after executing a plan by @jerop in
|
||||
[#19432](https://github.com/google-gemini/gemini-cli/pull/19432)
|
||||
- fix(core): create new McpClient on restart to apply updated config by @h30s in
|
||||
[#20126](https://github.com/google-gemini/gemini-cli/pull/20126)
|
||||
- Changelog for v0.30.0-preview.5 by @gemini-cli-robot in
|
||||
[#20107](https://github.com/google-gemini/gemini-cli/pull/20107)
|
||||
- Update packages. by @jacob314 in
|
||||
[#20152](https://github.com/google-gemini/gemini-cli/pull/20152)
|
||||
- Fix extension env dir loading issue by @chrstnb in
|
||||
[#20198](https://github.com/google-gemini/gemini-cli/pull/20198)
|
||||
- restrict /assign to help-wanted issues by @scidomino in
|
||||
[#20207](https://github.com/google-gemini/gemini-cli/pull/20207)
|
||||
- feat(plan): inject message when user manually exits Plan mode by @jerop in
|
||||
[#20203](https://github.com/google-gemini/gemini-cli/pull/20203)
|
||||
- feat(extensions): enforce folder trust for local extension install by @galz10
|
||||
in [#19703](https://github.com/google-gemini/gemini-cli/pull/19703)
|
||||
- feat(hooks): adds support for RuntimeHook functions. by @mbleigh in
|
||||
[#19598](https://github.com/google-gemini/gemini-cli/pull/19598)
|
||||
- Docs: Update UI links. by @jkcinouye in
|
||||
[#20224](https://github.com/google-gemini/gemini-cli/pull/20224)
|
||||
- feat: prompt users to run /terminal-setup with yes/no by @ishaanxgupta in
|
||||
[#16235](https://github.com/google-gemini/gemini-cli/pull/16235)
|
||||
- fix: additional high vulnerabilities (minimatch, cross-spawn) by @adamfweidman
|
||||
in [#20221](https://github.com/google-gemini/gemini-cli/pull/20221)
|
||||
- feat(telemetry): Add context breakdown to API response event by @SandyTao520
|
||||
in [#19699](https://github.com/google-gemini/gemini-cli/pull/19699)
|
||||
- Docs: Add nested sub-folders for related topics by @g-samroberts in
|
||||
[#20235](https://github.com/google-gemini/gemini-cli/pull/20235)
|
||||
- feat(plan): support automatic model switching for Plan Mode by @jerop in
|
||||
[#20240](https://github.com/google-gemini/gemini-cli/pull/20240)
|
||||
|
||||
**Full Changelog**:
|
||||
https://github.com/google-gemini/gemini-cli/compare/v0.31.0-preview.3...v0.32.0-preview.0
|
||||
https://github.com/google-gemini/gemini-cli/compare/v0.30.0-preview.6...v0.31.0-preview.1
|
||||
|
||||
+12
-12
@@ -5,18 +5,18 @@ and parameters.
|
||||
|
||||
## CLI commands
|
||||
|
||||
| Command | Description | Example |
|
||||
| ---------------------------------- | ---------------------------------- | ------------------------------------------------------------ |
|
||||
| `gemini` | Start interactive REPL | `gemini` |
|
||||
| `gemini "query"` | Query non-interactively, then exit | `gemini "explain this project"` |
|
||||
| `cat file \| gemini` | Process piped content | `cat logs.txt \| gemini`<br>`Get-Content logs.txt \| gemini` |
|
||||
| `gemini -i "query"` | Execute and continue interactively | `gemini -i "What is the purpose of this project?"` |
|
||||
| `gemini -r "latest"` | Continue most recent session | `gemini -r "latest"` |
|
||||
| `gemini -r "latest" "query"` | Continue session with a new prompt | `gemini -r "latest" "Check for type errors"` |
|
||||
| `gemini -r "<session-id>" "query"` | Resume session by ID | `gemini -r "abc123" "Finish this PR"` |
|
||||
| `gemini update` | Update to latest version | `gemini update` |
|
||||
| `gemini extensions` | Manage extensions | See [Extensions Management](#extensions-management) |
|
||||
| `gemini mcp` | Configure MCP servers | See [MCP Server Management](#mcp-server-management) |
|
||||
| Command | Description | Example |
|
||||
| ---------------------------------- | ---------------------------------- | --------------------------------------------------- |
|
||||
| `gemini` | Start interactive REPL | `gemini` |
|
||||
| `gemini "query"` | Query non-interactively, then exit | `gemini "explain this project"` |
|
||||
| `cat file \| gemini` | Process piped content | `cat logs.txt \| gemini` |
|
||||
| `gemini -i "query"` | Execute and continue interactively | `gemini -i "What is the purpose of this project?"` |
|
||||
| `gemini -r "latest"` | Continue most recent session | `gemini -r "latest"` |
|
||||
| `gemini -r "latest" "query"` | Continue session with a new prompt | `gemini -r "latest" "Check for type errors"` |
|
||||
| `gemini -r "<session-id>" "query"` | Resume session by ID | `gemini -r "abc123" "Finish this PR"` |
|
||||
| `gemini update` | Update to latest version | `gemini update` |
|
||||
| `gemini extensions` | Manage extensions | See [Extensions Management](#extensions-management) |
|
||||
| `gemini mcp` | Configure MCP servers | See [MCP Server Management](#mcp-server-management) |
|
||||
|
||||
### Positional arguments
|
||||
|
||||
|
||||
@@ -278,20 +278,11 @@ Let's create a global command that asks the model to refactor a piece of code.
|
||||
First, ensure the user commands directory exists, then create a `refactor`
|
||||
subdirectory for organization and the final TOML file.
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.gemini/commands/refactor
|
||||
touch ~/.gemini/commands/refactor/pure.toml
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.gemini\commands\refactor"
|
||||
New-Item -ItemType File -Force -Path "$env:USERPROFILE\.gemini\commands\refactor\pure.toml"
|
||||
```
|
||||
|
||||
**2. Add the content to the file:**
|
||||
|
||||
Open `~/.gemini/commands/refactor/pure.toml` in your editor and add the
|
||||
|
||||
@@ -203,15 +203,6 @@ with the actual Gemini CLI process, which inherits the environment variable.
|
||||
This makes it significantly more difficult for a user to bypass the enforced
|
||||
settings.
|
||||
|
||||
**PowerShell Profile (Windows alternative):**
|
||||
|
||||
On Windows, administrators can achieve similar results by adding the environment
|
||||
variable to the system-wide or user-specific PowerShell profile:
|
||||
|
||||
```powershell
|
||||
Add-Content -Path $PROFILE -Value '$env:GEMINI_CLI_SYSTEM_SETTINGS_PATH="C:\ProgramData\gemini-cli\settings.json"'
|
||||
```
|
||||
|
||||
## User isolation in shared environments
|
||||
|
||||
In shared compute environments (like ML experiment runners or shared build
|
||||
@@ -223,22 +214,12 @@ use the `GEMINI_CLI_HOME` environment variable to point to a unique directory
|
||||
for a specific user or job. The CLI will create a `.gemini` folder inside the
|
||||
specified path.
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
# Isolate state for a specific job
|
||||
export GEMINI_CLI_HOME="/tmp/gemini-job-123"
|
||||
gemini
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
# Isolate state for a specific job
|
||||
$env:GEMINI_CLI_HOME="C:\temp\gemini-job-123"
|
||||
gemini
|
||||
```
|
||||
|
||||
## Restricting tool access
|
||||
|
||||
You can significantly enhance security by controlling which tools the Gemini
|
||||
|
||||
+16
-46
@@ -55,27 +55,12 @@ from your organization's registry.
|
||||
```bash
|
||||
# Enable sandboxing with command flag
|
||||
gemini -s -p "analyze the code structure"
|
||||
```
|
||||
|
||||
**Use environment variable**
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
# Use environment variable
|
||||
export GEMINI_SANDBOX=true
|
||||
gemini -p "run the test suite"
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
$env:GEMINI_SANDBOX="true"
|
||||
gemini -p "run the test suite"
|
||||
```
|
||||
|
||||
**Configure in settings.json**
|
||||
|
||||
```json
|
||||
# Configure in settings.json
|
||||
{
|
||||
"tools": {
|
||||
"sandbox": "docker"
|
||||
@@ -106,59 +91,44 @@ Built-in profiles (set via `SEATBELT_PROFILE` env var):
|
||||
### Custom sandbox flags
|
||||
|
||||
For container-based sandboxing, you can inject custom flags into the `docker` or
|
||||
`podman` command using the `SANDBOX_FLAGS` environment variable. This is useful
|
||||
for advanced configurations, such as disabling security features for specific
|
||||
use cases.
|
||||
`podman` command using the `tools.sandboxFlags` setting in your `settings.json`
|
||||
or the `SANDBOX_FLAGS` environment variable. This is useful for advanced
|
||||
configurations, such as disabling security features for specific use cases.
|
||||
|
||||
**Example (Podman)**:
|
||||
**Example (`settings.json`)**:
|
||||
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
"sandboxFlags": "--security-opt label=disable"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Example (Environment variable)**:
|
||||
|
||||
To disable SELinux labeling for volume mounts, you can set the following:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
export SANDBOX_FLAGS="--security-opt label=disable"
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
$env:SANDBOX_FLAGS="--security-opt label=disable"
|
||||
```
|
||||
|
||||
Multiple flags can be provided as a space-separated string:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
export SANDBOX_FLAGS="--flag1 --flag2=value"
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
$env:SANDBOX_FLAGS="--flag1 --flag2=value"
|
||||
```
|
||||
|
||||
## Linux UID/GID handling
|
||||
|
||||
The sandbox automatically handles user permissions on Linux. Override these
|
||||
permissions with:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
export SANDBOX_SET_UID_GID=true # Force host UID/GID
|
||||
export SANDBOX_SET_UID_GID=false # Disable UID/GID mapping
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
$env:SANDBOX_SET_UID_GID="true" # Force host UID/GID
|
||||
$env:SANDBOX_SET_UID_GID="false" # Disable UID/GID mapping
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common issues
|
||||
|
||||
@@ -113,6 +113,7 @@ they appear in the UI.
|
||||
|
||||
| UI Label | Setting | Description | Default |
|
||||
| -------------------------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
|
||||
| Sandbox Flags | `tools.sandboxFlags` | Additional flags to pass to the sandbox container engine (Docker or Podman). Environment variables can be used and will be expanded. | `""` |
|
||||
| Enable Interactive Shell | `tools.shell.enableInteractiveShell` | Use node-pty for an interactive shell experience. Fallback to child_process still applies. | `true` |
|
||||
| Show Color | `tools.shell.showColor` | Show color in shell output. | `false` |
|
||||
| Use Ripgrep | `tools.useRipgrep` | Use ripgrep for file content search instead of the fallback implementation. Provides faster search performance. | `true` |
|
||||
|
||||
@@ -103,52 +103,23 @@ Before using either method below, complete these steps:
|
||||
|
||||
1. Set your Google Cloud project ID:
|
||||
- For telemetry in a separate project from inference:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
export OTLP_GOOGLE_CLOUD_PROJECT="your-telemetry-project-id"
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
$env:OTLP_GOOGLE_CLOUD_PROJECT="your-telemetry-project-id"
|
||||
```
|
||||
|
||||
- For telemetry in the same project as inference:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
export GOOGLE_CLOUD_PROJECT="your-project-id"
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
$env:GOOGLE_CLOUD_PROJECT="your-project-id"
|
||||
```
|
||||
|
||||
2. Authenticate with Google Cloud:
|
||||
- If using a user account:
|
||||
```bash
|
||||
gcloud auth application-default login
|
||||
```
|
||||
- If using a service account:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/your/service-account.json"
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
$env:GOOGLE_APPLICATION_CREDENTIALS="C:\path\to\your\service-account.json"
|
||||
```
|
||||
|
||||
3. Make sure your account or service account has these IAM roles:
|
||||
- Cloud Trace Agent
|
||||
- Monitoring Metric Writer
|
||||
|
||||
@@ -37,18 +37,10 @@ output.
|
||||
|
||||
Pipe a file:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
cat error.log | gemini "Explain why this failed"
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
Get-Content error.log | gemini "Explain why this failed"
|
||||
```
|
||||
|
||||
Pipe a command:
|
||||
|
||||
```bash
|
||||
@@ -65,10 +57,7 @@ results to a file.
|
||||
You have a folder of Python scripts and want to generate a `README.md` for each
|
||||
one.
|
||||
|
||||
1. Save the following code as `generate_docs.sh` (or `generate_docs.ps1` for
|
||||
Windows):
|
||||
|
||||
**macOS/Linux (`generate_docs.sh`)**
|
||||
1. Save the following code as `generate_docs.sh`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
@@ -83,34 +72,13 @@ one.
|
||||
done
|
||||
```
|
||||
|
||||
**Windows PowerShell (`generate_docs.ps1`)**
|
||||
|
||||
```powershell
|
||||
# Loop through all Python files
|
||||
Get-ChildItem -Filter *.py | ForEach-Object {
|
||||
Write-Host "Generating docs for $($_.Name)..."
|
||||
|
||||
$newName = $_.Name -replace '\.py$', '.md'
|
||||
# Ask Gemini CLI to generate the documentation and print it to stdout
|
||||
gemini "Generate a Markdown documentation summary for @$($_.Name). Print the result to standard output." | Out-File -FilePath $newName -Encoding utf8
|
||||
}
|
||||
```
|
||||
|
||||
2. Make the script executable and run it in your directory:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
chmod +x generate_docs.sh
|
||||
./generate_docs.sh
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
.\generate_docs.ps1
|
||||
```
|
||||
|
||||
This creates a corresponding Markdown file for every Python file in the
|
||||
folder.
|
||||
|
||||
@@ -122,10 +90,7 @@ like `jq`. To get pure JSON data from the model, combine the
|
||||
|
||||
### Scenario: Extract and return structured data
|
||||
|
||||
1. Save the following script as `generate_json.sh` (or `generate_json.ps1` for
|
||||
Windows):
|
||||
|
||||
**macOS/Linux (`generate_json.sh`)**
|
||||
1. Save the following script as `generate_json.sh`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
@@ -140,35 +105,13 @@ like `jq`. To get pure JSON data from the model, combine the
|
||||
gemini --output-format json "Return a raw JSON object with keys 'version' and 'deps' from @package.json" | jq -r '.response' > data.json
|
||||
```
|
||||
|
||||
**Windows PowerShell (`generate_json.ps1`)**
|
||||
|
||||
```powershell
|
||||
# Ensure we are in a project root
|
||||
if (-not (Test-Path "package.json")) {
|
||||
Write-Error "Error: package.json not found."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Extract data (requires jq installed, or you can use ConvertFrom-Json)
|
||||
$output = gemini --output-format json "Return a raw JSON object with keys 'version' and 'deps' from @package.json" | ConvertFrom-Json
|
||||
$output.response | Out-File -FilePath data.json -Encoding utf8
|
||||
```
|
||||
|
||||
2. Run the script:
|
||||
|
||||
**macOS/Linux**
|
||||
2. Run `generate_json.sh`:
|
||||
|
||||
```bash
|
||||
chmod +x generate_json.sh
|
||||
./generate_json.sh
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
.\generate_json.ps1
|
||||
```
|
||||
|
||||
3. Check `data.json`. The file should look like this:
|
||||
|
||||
```json
|
||||
@@ -186,10 +129,8 @@ Use headless mode to perform custom, automated AI tasks.
|
||||
|
||||
### Scenario: Create a "Smart Commit" alias
|
||||
|
||||
You can add a function to your shell configuration to create a `git commit`
|
||||
wrapper that writes the message for you.
|
||||
|
||||
**macOS/Linux (Bash/Zsh)**
|
||||
You can add a function to your shell configuration (like `.zshrc` or `.bashrc`)
|
||||
to create a `git commit` wrapper that writes the message for you.
|
||||
|
||||
1. Open your `.zshrc` file (or `.bashrc` if you use Bash) in your preferred
|
||||
text editor.
|
||||
@@ -229,43 +170,6 @@ wrapper that writes the message for you.
|
||||
source ~/.zshrc
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
1. Open your PowerShell profile in your preferred text editor.
|
||||
|
||||
```powershell
|
||||
notepad $PROFILE
|
||||
```
|
||||
|
||||
2. Scroll to the very bottom of the file and paste this code:
|
||||
|
||||
```powershell
|
||||
function gcommit {
|
||||
# Get the diff of staged changes
|
||||
$diff = git diff --staged
|
||||
|
||||
if (-not $diff) {
|
||||
Write-Host "No staged changes to commit."
|
||||
return
|
||||
}
|
||||
|
||||
# Ask Gemini to write the message
|
||||
Write-Host "Generating commit message..."
|
||||
$msg = $diff | gemini "Write a concise Conventional Commit message for this diff. Output ONLY the message."
|
||||
|
||||
# Commit with the generated message
|
||||
git commit -m "$msg"
|
||||
}
|
||||
```
|
||||
|
||||
Save your file and exit.
|
||||
|
||||
3. Run this command to make the function available immediately:
|
||||
|
||||
```powershell
|
||||
. $PROFILE
|
||||
```
|
||||
|
||||
4. Use your new command:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -20,18 +20,10 @@ Most MCP servers require authentication. For GitHub, you need a PAT.
|
||||
**Read/Write** access to **Issues** and **Pull Requests**.
|
||||
3. Store it in your environment:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
export GITHUB_PERSONAL_ACCESS_TOKEN="github_pat_..."
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
$env:GITHUB_PERSONAL_ACCESS_TOKEN="github_pat_..."
|
||||
```
|
||||
|
||||
## How to configure Gemini CLI
|
||||
|
||||
You tell Gemini about new servers by editing your `settings.json`.
|
||||
|
||||
@@ -14,18 +14,10 @@ responding correctly.
|
||||
|
||||
1. Run the following command to create the folders:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
mkdir -p .gemini/skills/api-auditor/scripts
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
New-Item -ItemType Directory -Force -Path ".gemini\skills\api-auditor\scripts"
|
||||
```
|
||||
|
||||
### Create the definition
|
||||
|
||||
1. Create a file at `.gemini/skills/api-auditor/SKILL.md`. This tells the agent
|
||||
|
||||
@@ -189,18 +189,10 @@ Custom commands create shortcuts for complex prompts.
|
||||
|
||||
1. Create a `commands` directory and a subdirectory for your command group:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
mkdir -p commands/fs
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
New-Item -ItemType Directory -Force -Path "commands\fs"
|
||||
```
|
||||
|
||||
2. Create a file named `commands/fs/grep-code.toml`:
|
||||
|
||||
```toml
|
||||
@@ -260,18 +252,10 @@ Skills are activated only when needed, which saves context tokens.
|
||||
|
||||
1. Create a `skills` directory and a subdirectory for your skill:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
mkdir -p skills/security-audit
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
New-Item -ItemType Directory -Force -Path "skills\security-audit"
|
||||
```
|
||||
|
||||
2. Create a `skills/security-audit/SKILL.md` file:
|
||||
|
||||
```markdown
|
||||
|
||||
@@ -78,20 +78,11 @@ 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"
|
||||
```
|
||||
|
||||
To make this setting persistent, see
|
||||
[Persisting Environment Variables](#persisting-vars).
|
||||
|
||||
@@ -123,22 +114,12 @@ or the location where you want to run your jobs.
|
||||
|
||||
For example:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
# Replace with your project ID and desired location (e.g., 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 (e.g., us-central1)
|
||||
$env:GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"
|
||||
$env:GOOGLE_CLOUD_LOCATION="YOUR_PROJECT_LOCATION"
|
||||
```
|
||||
|
||||
To make any Vertex AI environment variable settings persistent, see
|
||||
[Persisting Environment Variables](#persisting-vars).
|
||||
|
||||
@@ -149,17 +130,9 @@ Consider this authentication method if you have Google Cloud CLI installed.
|
||||
> **Note:** If you have previously set `GOOGLE_API_KEY` or `GEMINI_API_KEY`, you
|
||||
> must unset them to use ADC:
|
||||
>
|
||||
> **macOS/Linux**
|
||||
>
|
||||
> ```bash
|
||||
> unset GOOGLE_API_KEY GEMINI_API_KEY
|
||||
> ```
|
||||
>
|
||||
> **Windows (PowerShell)**
|
||||
>
|
||||
> ```powershell
|
||||
> Remove-Item Env:\GOOGLE_API_KEY, Env:\GEMINI_API_KEY -ErrorAction Ignore
|
||||
> ```
|
||||
|
||||
1. Verify you have a Google Cloud project and Vertex AI API is enabled.
|
||||
|
||||
@@ -187,17 +160,9 @@ pipelines, or if your organization restricts user-based ADC or API key creation.
|
||||
> **Note:** If you have previously set `GOOGLE_API_KEY` or `GEMINI_API_KEY`, you
|
||||
> must unset them:
|
||||
>
|
||||
> **macOS/Linux**
|
||||
>
|
||||
> ```bash
|
||||
> unset GOOGLE_API_KEY GEMINI_API_KEY
|
||||
> ```
|
||||
>
|
||||
> **Windows (PowerShell)**
|
||||
>
|
||||
> ```powershell
|
||||
> Remove-Item Env:\GOOGLE_API_KEY, Env:\GEMINI_API_KEY -ErrorAction Ignore
|
||||
> ```
|
||||
|
||||
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
|
||||
@@ -206,20 +171,11 @@ pipelines, or if your organization restricts user-based ADC or API key creation.
|
||||
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"
|
||||
```
|
||||
|
||||
3. [Configure your Google Cloud Project](#set-gcp).
|
||||
|
||||
4. Start the CLI:
|
||||
@@ -239,20 +195,11 @@ pipelines, or if your organization restricts user-based ADC or API key creation.
|
||||
|
||||
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"
|
||||
```
|
||||
|
||||
> **Note:** If you see errors like
|
||||
> `"API keys are not supported by this API..."`, your organization might
|
||||
> restrict API key usage for this service. Try the other Vertex AI
|
||||
@@ -296,20 +243,11 @@ 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"
|
||||
```
|
||||
|
||||
To make this setting persistent, see
|
||||
[Persisting Environment Variables](#persisting-vars).
|
||||
|
||||
@@ -319,22 +257,16 @@ To avoid setting environment variables for every terminal session, you can
|
||||
persist them with the following methods:
|
||||
|
||||
1. **Add your environment variables to your shell configuration file:** Append
|
||||
the environment variable commands to your shell's startup file.
|
||||
|
||||
**macOS/Linux** (e.g., `~/.bashrc`, `~/.zshrc`, or `~/.profile`):
|
||||
the `export ...` commands to your shell's startup file (e.g., `~/.bashrc`,
|
||||
`~/.zshrc`, or `~/.profile`) and reload your shell (e.g.,
|
||||
`source ~/.bashrc`).
|
||||
|
||||
```bash
|
||||
# Example for .bashrc
|
||||
echo 'export GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"' >> ~/.bashrc
|
||||
source ~/.bashrc
|
||||
```
|
||||
|
||||
**Windows (PowerShell)** (e.g., `$PROFILE`):
|
||||
|
||||
```powershell
|
||||
Add-Content -Path $PROFILE -Value '$env:GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"'
|
||||
. $PROFILE
|
||||
```
|
||||
|
||||
> **Warning:** Be aware that when you export API keys or service account
|
||||
> paths in your shell configuration file, any process launched from that
|
||||
> shell can read them.
|
||||
@@ -342,13 +274,10 @@ persist them with the following methods:
|
||||
2. **Use a `.env` file:** Create a `.gemini/.env` file in your project
|
||||
directory or home directory. Gemini CLI automatically loads variables from
|
||||
the first `.env` file it finds, searching up from the current directory,
|
||||
then in your home directory's `.gemini/.env` (e.g., `~/.gemini/.env` or
|
||||
`%USERPROFILE%\.gemini\.env`).
|
||||
then in `~/.gemini/.env` or `~/.env`. `.gemini/.env` is recommended.
|
||||
|
||||
Example for user-wide settings:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.gemini
|
||||
cat >> ~/.gemini/.env <<'EOF'
|
||||
@@ -357,16 +286,6 @@ persist them with the following methods:
|
||||
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
|
||||
```
|
||||
|
||||
Variables are loaded from the first file found, not merged.
|
||||
|
||||
## Running in Google Cloud environments <a id="cloud-env"></a>
|
||||
|
||||
@@ -13,7 +13,7 @@ installation methods, and release types.
|
||||
- "Casual" usage: 4GB+ RAM (short sessions, common tasks and edits)
|
||||
- "Power" usage: 16GB+ RAM (long sessions, large codebases, deep context)
|
||||
- **Runtime:** Node.js 20.0.0+
|
||||
- **Shell:** Bash, Zsh, or PowerShell
|
||||
- **Shell:** Bash or Zsh
|
||||
- **Location:**
|
||||
[Gemini Code Assist supported locations](https://developers.google.com/gemini-code-assist/resources/available-locations#americas)
|
||||
- **Internet connection required**
|
||||
|
||||
@@ -167,8 +167,6 @@ try {
|
||||
Run hook scripts manually with sample JSON input to verify they behave as
|
||||
expected before hooking them up to the CLI.
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
# Create test input
|
||||
cat > test-input.json << 'EOF'
|
||||
@@ -189,30 +187,7 @@ cat test-input.json | .gemini/hooks/my-hook.sh
|
||||
|
||||
# Check exit code
|
||||
echo "Exit code: $?"
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
# Create test input
|
||||
@"
|
||||
{
|
||||
"session_id": "test-123",
|
||||
"cwd": "C:\\temp\\test",
|
||||
"hook_event_name": "BeforeTool",
|
||||
"tool_name": "write_file",
|
||||
"tool_input": {
|
||||
"file_path": "test.txt",
|
||||
"content": "Test content"
|
||||
}
|
||||
}
|
||||
"@ | Out-File -FilePath test-input.json -Encoding utf8
|
||||
|
||||
# Test the hook
|
||||
Get-Content test-input.json | .\.gemini\hooks\my-hook.ps1
|
||||
|
||||
# Check exit code
|
||||
Write-Host "Exit code: $LASTEXITCODE"
|
||||
```
|
||||
|
||||
### Check exit codes
|
||||
@@ -358,7 +333,7 @@ tool_name=$(echo "$input" | jq -r '.tool_name')
|
||||
|
||||
### Make scripts executable
|
||||
|
||||
Always make hook scripts executable on macOS/Linux:
|
||||
Always make hook scripts executable:
|
||||
|
||||
```bash
|
||||
chmod +x .gemini/hooks/*.sh
|
||||
@@ -366,10 +341,6 @@ chmod +x .gemini/hooks/*.js
|
||||
|
||||
```
|
||||
|
||||
**Windows Note**: On Windows, PowerShell scripts (`.ps1`) don't use `chmod`, but
|
||||
you may need to ensure your execution policy allows them to run (e.g.,
|
||||
`Set-ExecutionPolicy RemoteSigned -Scope CurrentUser`).
|
||||
|
||||
### Version control
|
||||
|
||||
Commit hooks to share with your team:
|
||||
@@ -510,9 +481,6 @@ ls -la .gemini/hooks/my-hook.sh
|
||||
chmod +x .gemini/hooks/my-hook.sh
|
||||
```
|
||||
|
||||
**Windows Note**: On Windows, ensure your execution policy allows running
|
||||
scripts (e.g., `Get-ExecutionPolicy`).
|
||||
|
||||
**Verify script path:** Ensure the path in `settings.json` resolves correctly.
|
||||
|
||||
```bash
|
||||
|
||||
@@ -28,8 +28,6 @@ Create a directory for hooks and a simple logging script.
|
||||
> This example uses `jq` to parse JSON. If you don't have it installed, you can
|
||||
> perform similar logic using Node.js or Python.
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
mkdir -p .gemini/hooks
|
||||
cat > .gemini/hooks/log-tools.sh << 'EOF'
|
||||
@@ -54,28 +52,6 @@ EOF
|
||||
chmod +x .gemini/hooks/log-tools.sh
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
New-Item -ItemType Directory -Force -Path ".gemini\hooks"
|
||||
@"
|
||||
# Read hook input from stdin
|
||||
`$inputJson = `$input | Out-String | ConvertFrom-Json
|
||||
|
||||
# Extract tool name
|
||||
`$toolName = `$inputJson.tool_name
|
||||
|
||||
# Log to stderr (visible in terminal if hook fails, or captured in logs)
|
||||
[Console]::Error.WriteLine("Logging tool: `$toolName")
|
||||
|
||||
# Log to file
|
||||
"[`$(Get-Date -Format 'o')] Tool executed: `$toolName" | Out-File -FilePath ".gemini\tool-log.txt" -Append -Encoding utf8
|
||||
|
||||
# Return success with empty JSON
|
||||
"{}"
|
||||
"@ | Out-File -FilePath ".gemini\hooks\log-tools.ps1" -Encoding utf8
|
||||
```
|
||||
|
||||
## Exit Code Strategies
|
||||
|
||||
There are two ways to control or block an action in Gemini CLI:
|
||||
|
||||
@@ -177,18 +177,10 @@ standalone terminal and want to manually associate it with a specific IDE
|
||||
instance, you can set the `GEMINI_CLI_IDE_PID` environment variable to the
|
||||
process ID (PID) of your IDE.
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
export GEMINI_CLI_IDE_PID=12345
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
$env:GEMINI_CLI_IDE_PID=12345
|
||||
```
|
||||
|
||||
When this variable is set, Gemini CLI will skip automatic detection and attempt
|
||||
to connect using the provided PID.
|
||||
|
||||
|
||||
@@ -756,6 +756,12 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
- **Default:** `undefined`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`tools.sandboxFlags`** (string):
|
||||
- **Description:** Additional flags to pass to the sandbox container engine
|
||||
(Docker or Podman). Environment variables can be used and will be expanded.
|
||||
- **Default:** `""`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`tools.shell.enableInteractiveShell`** (boolean):
|
||||
- **Description:** Use node-pty for an interactive shell experience. Fallback
|
||||
to child_process still applies.
|
||||
@@ -1332,8 +1338,7 @@ the `advanced.excludedEnvVars` setting in your `settings.json` file.
|
||||
- **`GEMINI_MODEL`**:
|
||||
- Specifies the default Gemini model to use.
|
||||
- Overrides the hardcoded default
|
||||
- Example: `export GEMINI_MODEL="gemini-3-flash-preview"` (Windows PowerShell:
|
||||
`$env:GEMINI_MODEL="gemini-3-flash-preview"`)
|
||||
- Example: `export GEMINI_MODEL="gemini-3-flash-preview"`
|
||||
- **`GEMINI_CLI_IDE_PID`**:
|
||||
- Manually specifies the PID of the IDE process to use for integration. This
|
||||
is useful when running Gemini CLI in a standalone terminal while still
|
||||
@@ -1345,14 +1350,12 @@ the `advanced.excludedEnvVars` setting in your `settings.json` file.
|
||||
- By default, this is the user's system home directory. The CLI will create a
|
||||
`.gemini` folder inside this directory.
|
||||
- Useful for shared compute environments or keeping CLI state isolated.
|
||||
- Example: `export GEMINI_CLI_HOME="/path/to/user/config"` (Windows
|
||||
PowerShell: `$env:GEMINI_CLI_HOME="C:\path\to\user\config"`)
|
||||
- Example: `export GEMINI_CLI_HOME="/path/to/user/config"`
|
||||
- **`GOOGLE_API_KEY`**:
|
||||
- Your Google Cloud API key.
|
||||
- Required for using Vertex AI in express mode.
|
||||
- Ensure you have the necessary permissions.
|
||||
- Example: `export GOOGLE_API_KEY="YOUR_GOOGLE_API_KEY"` (Windows PowerShell:
|
||||
`$env:GOOGLE_API_KEY="YOUR_GOOGLE_API_KEY"`).
|
||||
- Example: `export GOOGLE_API_KEY="YOUR_GOOGLE_API_KEY"`.
|
||||
- **`GOOGLE_CLOUD_PROJECT`**:
|
||||
- Your Google Cloud Project ID.
|
||||
- Required for using Code Assist or Vertex AI.
|
||||
@@ -1363,23 +1366,18 @@ the `advanced.excludedEnvVars` setting in your `settings.json` file.
|
||||
you have `GOOGLE_CLOUD_PROJECT` set in your global environment in Cloud
|
||||
Shell, it will be overridden by this default. To use a different project in
|
||||
Cloud Shell, you must define `GOOGLE_CLOUD_PROJECT` in a `.env` file.
|
||||
- Example: `export GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"` (Windows
|
||||
PowerShell: `$env:GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"`).
|
||||
- Example: `export GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"`.
|
||||
- **`GOOGLE_APPLICATION_CREDENTIALS`** (string):
|
||||
- **Description:** The path to your Google Application Credentials JSON file.
|
||||
- **Example:**
|
||||
`export GOOGLE_APPLICATION_CREDENTIALS="/path/to/your/credentials.json"`
|
||||
(Windows PowerShell:
|
||||
`$env:GOOGLE_APPLICATION_CREDENTIALS="C:\path\to\your\credentials.json"`)
|
||||
- **`GOOGLE_GENAI_API_VERSION`**:
|
||||
- Specifies the API version to use for Gemini API requests.
|
||||
- When set, overrides the default API version used by the SDK.
|
||||
- Example: `export GOOGLE_GENAI_API_VERSION="v1"` (Windows PowerShell:
|
||||
`$env:GOOGLE_GENAI_API_VERSION="v1"`)
|
||||
- Example: `export GOOGLE_GENAI_API_VERSION="v1"`
|
||||
- **`OTLP_GOOGLE_CLOUD_PROJECT`**:
|
||||
- Your Google Cloud Project ID for Telemetry in Google Cloud
|
||||
- Example: `export OTLP_GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"` (Windows
|
||||
PowerShell: `$env:OTLP_GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"`).
|
||||
- Example: `export OTLP_GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"`.
|
||||
- **`GEMINI_TELEMETRY_ENABLED`**:
|
||||
- Set to `true` or `1` to enable telemetry. Any other value is treated as
|
||||
disabling it.
|
||||
@@ -1407,8 +1405,7 @@ the `advanced.excludedEnvVars` setting in your `settings.json` file.
|
||||
- **`GOOGLE_CLOUD_LOCATION`**:
|
||||
- Your Google Cloud Project Location (e.g., us-central1).
|
||||
- Required for using Vertex AI in non-express mode.
|
||||
- Example: `export GOOGLE_CLOUD_LOCATION="YOUR_PROJECT_LOCATION"` (Windows
|
||||
PowerShell: `$env:GOOGLE_CLOUD_LOCATION="YOUR_PROJECT_LOCATION"`).
|
||||
- Example: `export GOOGLE_CLOUD_LOCATION="YOUR_PROJECT_LOCATION"`.
|
||||
- **`GEMINI_SANDBOX`**:
|
||||
- Alternative to the `sandbox` setting in `settings.json`.
|
||||
- Accepts `true`, `false`, `docker`, `podman`, or a custom command string.
|
||||
|
||||
@@ -10,19 +10,9 @@ confirmation.
|
||||
To create your first policy:
|
||||
|
||||
1. **Create the policy directory** if it doesn't exist:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.gemini/policies
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.gemini\policies"
|
||||
```
|
||||
|
||||
2. **Create a new policy file** (e.g., `~/.gemini/policies/my-rules.toml`). You
|
||||
can use any filename ending in `.toml`; all such files in this directory
|
||||
will be loaded and combined:
|
||||
|
||||
@@ -88,18 +88,10 @@ You can configure your Google Cloud Project ID using an environment variable.
|
||||
|
||||
Set the `GOOGLE_CLOUD_PROJECT` environment variable in your shell:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
export GOOGLE_CLOUD_PROJECT="your-project-id"
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
$env:GOOGLE_CLOUD_PROJECT="your-project-id"
|
||||
```
|
||||
|
||||
To make this setting permanent, add this line to your shell's startup file
|
||||
(e.g., `~/.bashrc`, `~/.zshrc`).
|
||||
|
||||
|
||||
@@ -55,13 +55,10 @@ topics on:
|
||||
- Set the `NODE_USE_SYSTEM_CA=1` environment variable to tell Node.js to use
|
||||
the operating system's native certificate store (where corporate
|
||||
certificates are typically already installed).
|
||||
- Example: `export NODE_USE_SYSTEM_CA=1` (Windows PowerShell:
|
||||
`$env:NODE_USE_SYSTEM_CA=1`)
|
||||
- Example: `export NODE_USE_SYSTEM_CA=1`
|
||||
- Set the `NODE_EXTRA_CA_CERTS` environment variable to the absolute path of
|
||||
your corporate root CA certificate file.
|
||||
- Example: `export NODE_EXTRA_CA_CERTS=/path/to/your/corporate-ca.crt`
|
||||
(Windows PowerShell:
|
||||
`$env:NODE_EXTRA_CA_CERTS="C:\path\to\your\corporate-ca.crt"`)
|
||||
|
||||
## Common error messages and solutions
|
||||
|
||||
|
||||
Generated
+9
-9
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@google/gemini-cli",
|
||||
"version": "0.33.0-nightly.20260228.1ca5c05d0",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@google/gemini-cli",
|
||||
"version": "0.33.0-nightly.20260228.1ca5c05d0",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
@@ -17056,7 +17056,7 @@
|
||||
},
|
||||
"packages/a2a-server": {
|
||||
"name": "@google/gemini-cli-a2a-server",
|
||||
"version": "0.33.0-nightly.20260228.1ca5c05d0",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"dependencies": {
|
||||
"@a2a-js/sdk": "^0.3.8",
|
||||
"@google-cloud/storage": "^7.16.0",
|
||||
@@ -17114,7 +17114,7 @@
|
||||
},
|
||||
"packages/cli": {
|
||||
"name": "@google/gemini-cli",
|
||||
"version": "0.33.0-nightly.20260228.1ca5c05d0",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "^0.12.0",
|
||||
@@ -17197,7 +17197,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@google/gemini-cli-core",
|
||||
"version": "0.33.0-nightly.20260228.1ca5c05d0",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@a2a-js/sdk": "^0.3.8",
|
||||
@@ -17462,7 +17462,7 @@
|
||||
},
|
||||
"packages/devtools": {
|
||||
"name": "@google/gemini-cli-devtools",
|
||||
"version": "0.33.0-nightly.20260228.1ca5c05d0",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"ws": "^8.16.0"
|
||||
@@ -17477,7 +17477,7 @@
|
||||
},
|
||||
"packages/sdk": {
|
||||
"name": "@google/gemini-cli-sdk",
|
||||
"version": "0.33.0-nightly.20260228.1ca5c05d0",
|
||||
"version": "0.29.0-nightly.20260203.71f46f116",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@google/gemini-cli-core": "file:../core",
|
||||
@@ -17494,7 +17494,7 @@
|
||||
},
|
||||
"packages/test-utils": {
|
||||
"name": "@google/gemini-cli-test-utils",
|
||||
"version": "0.33.0-nightly.20260228.1ca5c05d0",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@google/gemini-cli-core": "file:../core",
|
||||
@@ -17511,7 +17511,7 @@
|
||||
},
|
||||
"packages/vscode-ide-companion": {
|
||||
"name": "gemini-cli-vscode-ide-companion",
|
||||
"version": "0.33.0-nightly.20260228.1ca5c05d0",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"license": "LICENSE",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.23.0",
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli",
|
||||
"version": "0.33.0-nightly.20260228.1ca5c05d0",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
@@ -14,7 +14,7 @@
|
||||
"url": "git+https://github.com/google-gemini/gemini-cli.git"
|
||||
},
|
||||
"config": {
|
||||
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.33.0-nightly.20260228.1ca5c05d0"
|
||||
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.30.0-nightly.20260210.a2174751d"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "cross-env NODE_ENV=development node scripts/start.js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli-a2a-server",
|
||||
"version": "0.33.0-nightly.20260228.1ca5c05d0",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"description": "Gemini CLI A2A Server",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -28,7 +28,6 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
const mockConfig = {
|
||||
...params,
|
||||
initialize: vi.fn(),
|
||||
waitForMcpInit: vi.fn(),
|
||||
refreshAuth: vi.fn(),
|
||||
getExperiments: vi.fn().mockReturnValue({
|
||||
flags: {
|
||||
@@ -95,7 +94,6 @@ describe('loadConfig', () => {
|
||||
const mockConfig = {
|
||||
...(params as object),
|
||||
initialize: vi.fn(),
|
||||
waitForMcpInit: vi.fn(),
|
||||
refreshAuth: vi.fn(),
|
||||
getExperiments: vi.fn().mockReturnValue({
|
||||
flags: {
|
||||
|
||||
@@ -166,8 +166,6 @@ export async function loadConfig(
|
||||
|
||||
// Needed to initialize ToolRegistry, and git checkpointing if enabled
|
||||
await config.initialize();
|
||||
|
||||
await config.waitForMcpInit();
|
||||
startupProfiler.flush(config);
|
||||
|
||||
await refreshAuthentication(config, adcFilePath, 'Config');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli",
|
||||
"version": "0.33.0-nightly.20260228.1ca5c05d0",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"description": "Gemini CLI",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
@@ -26,7 +26,7 @@
|
||||
"dist"
|
||||
],
|
||||
"config": {
|
||||
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.33.0-nightly.20260228.1ca5c05d0"
|
||||
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.30.0-nightly.20260210.a2174751d"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "^0.12.0",
|
||||
|
||||
@@ -104,5 +104,7 @@ export async function loadSandboxConfig(
|
||||
const image =
|
||||
process.env['GEMINI_SANDBOX_IMAGE'] ?? packageJson?.config?.sandboxImageUri;
|
||||
|
||||
return command && image ? { command, image } : undefined;
|
||||
const flags = settings.tools?.sandboxFlags;
|
||||
|
||||
return command && image ? { command, image, flags } : undefined;
|
||||
}
|
||||
|
||||
@@ -1245,6 +1245,18 @@ const SETTINGS_SCHEMA = {
|
||||
`,
|
||||
showInDialog: false,
|
||||
},
|
||||
sandboxFlags: {
|
||||
type: 'string',
|
||||
label: 'Sandbox Flags',
|
||||
category: 'Tools',
|
||||
requiresRestart: true,
|
||||
default: '',
|
||||
description: oneLine`
|
||||
Additional flags to pass to the sandbox container engine (Docker or Podman).
|
||||
Environment variables can be used and will be expanded.
|
||||
`,
|
||||
showInDialog: true,
|
||||
},
|
||||
shell: {
|
||||
type: 'object',
|
||||
label: 'Shell',
|
||||
|
||||
@@ -604,7 +604,6 @@ const mockUIActions: UIActions = {
|
||||
revealCleanUiDetailsTemporarily: vi.fn(),
|
||||
handleWarning: vi.fn(),
|
||||
setEmbeddedShellFocused: vi.fn(),
|
||||
setActivePtyId: vi.fn(),
|
||||
dismissBackgroundShell: vi.fn(),
|
||||
setActiveBackgroundShellPid: vi.fn(),
|
||||
setIsBackgroundShellListOpen: vi.fn(),
|
||||
|
||||
@@ -1109,7 +1109,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
backgroundShells,
|
||||
dismissBackgroundShell,
|
||||
retryStatus,
|
||||
setActivePtyId,
|
||||
} = useGeminiStream(
|
||||
config.getGeminiClient(),
|
||||
historyManager.history,
|
||||
@@ -2510,7 +2509,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
revealCleanUiDetailsTemporarily,
|
||||
handleWarning,
|
||||
setEmbeddedShellFocused,
|
||||
setActivePtyId,
|
||||
dismissBackgroundShell,
|
||||
setActiveBackgroundShellPid,
|
||||
setIsBackgroundShellListOpen,
|
||||
@@ -2603,7 +2601,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
revealCleanUiDetailsTemporarily,
|
||||
handleWarning,
|
||||
setEmbeddedShellFocused,
|
||||
setActivePtyId,
|
||||
dismissBackgroundShell,
|
||||
setActiveBackgroundShellPid,
|
||||
setIsBackgroundShellListOpen,
|
||||
|
||||
@@ -26,6 +26,7 @@ export const StickyHeader: React.FC<StickyHeaderProps> = ({
|
||||
containerRef,
|
||||
}) => (
|
||||
<Box
|
||||
ref={containerRef}
|
||||
sticky
|
||||
minHeight={1}
|
||||
flexShrink={0}
|
||||
@@ -57,7 +58,6 @@ export const StickyHeader: React.FC<StickyHeaderProps> = ({
|
||||
}
|
||||
>
|
||||
<Box
|
||||
ref={containerRef}
|
||||
borderStyle="round"
|
||||
width={width}
|
||||
borderColor={borderColor}
|
||||
|
||||
@@ -78,7 +78,7 @@ export const ShellToolMessage: React.FC<ShellToolMessageProps> = ({
|
||||
embeddedShellFocused,
|
||||
);
|
||||
|
||||
const { setEmbeddedShellFocused, setActivePtyId } = useUIActions();
|
||||
const { setEmbeddedShellFocused } = useUIActions();
|
||||
const wasFocusedRef = React.useRef(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -102,14 +102,13 @@ export const ShellToolMessage: React.FC<ShellToolMessageProps> = ({
|
||||
|
||||
const handleFocus = () => {
|
||||
if (isThisShellFocusable) {
|
||||
setActivePtyId(ptyId ?? null);
|
||||
setEmbeddedShellFocused(true);
|
||||
}
|
||||
};
|
||||
|
||||
useMouseClick(headerRef, handleFocus, { isActive: true });
|
||||
useMouseClick(headerRef, handleFocus, { isActive: !!isThisShellFocusable });
|
||||
|
||||
useMouseClick(contentRef, handleFocus, { isActive: true });
|
||||
useMouseClick(contentRef, handleFocus, { isActive: !!isThisShellFocusable });
|
||||
|
||||
const { shouldShowFocusHint } = useFocusHint(
|
||||
isThisShellFocusable,
|
||||
|
||||
@@ -16,11 +16,12 @@ import {
|
||||
} from '../../constants.js';
|
||||
import { theme } from '../../semantic-colors.js';
|
||||
import {
|
||||
type Config,
|
||||
SHELL_TOOL_NAME,
|
||||
isCompletedAskUserTool
|
||||
isCompletedAskUserTool,
|
||||
type ToolResultDisplay,
|
||||
CoreToolCallStatus,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { Config, ToolResultDisplay ,
|
||||
CoreToolCallStatus} from '@google/gemini-cli-core';
|
||||
import { useInactivityTimer } from '../../hooks/useInactivityTimer.js';
|
||||
import { formatCommand } from '../../utils/keybindingUtils.js';
|
||||
import { Command } from '../../../config/keyBindings.js';
|
||||
@@ -43,10 +44,14 @@ export function isShellTool(name: string): boolean {
|
||||
*/
|
||||
export function isThisShellFocusable(
|
||||
name: string,
|
||||
_status: CoreToolCallStatus,
|
||||
_config?: Config,
|
||||
status: CoreToolCallStatus,
|
||||
config?: Config,
|
||||
): boolean {
|
||||
return isShellTool(name);
|
||||
return !!(
|
||||
isShellTool(name) &&
|
||||
status === CoreToolCallStatus.Executing &&
|
||||
config?.getEnableInteractiveShell()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,13 +59,14 @@ export function isThisShellFocusable(
|
||||
*/
|
||||
export function isThisShellFocused(
|
||||
name: string,
|
||||
_status: CoreToolCallStatus,
|
||||
status: CoreToolCallStatus,
|
||||
ptyId?: number,
|
||||
activeShellPtyId?: number | null,
|
||||
embeddedShellFocused?: boolean,
|
||||
): boolean {
|
||||
return !!(
|
||||
isShellTool(name) &&
|
||||
status === CoreToolCallStatus.Executing &&
|
||||
ptyId === activeShellPtyId &&
|
||||
embeddedShellFocused
|
||||
);
|
||||
|
||||
@@ -5,20 +5,12 @@
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import {
|
||||
useState,
|
||||
useRef,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useLayoutEffect,
|
||||
useEffect,
|
||||
} from 'react';
|
||||
import { Box, ResizeObserver, getBoundingBox, type DOMElement } from 'ink';
|
||||
import { useState, useRef, useCallback, useMemo, useLayoutEffect } from 'react';
|
||||
import { Box, ResizeObserver, type DOMElement } from 'ink';
|
||||
import { useKeypress, type Key } from '../../hooks/useKeypress.js';
|
||||
import { useScrollable } from '../../contexts/ScrollProvider.js';
|
||||
import { useAnimatedScrollbar } from '../../hooks/useAnimatedScrollbar.js';
|
||||
import { useBatchedScroll } from '../../hooks/useBatchedScroll.js';
|
||||
import { useMouse, type MouseEvent } from '../../hooks/useMouse.js';
|
||||
import { keyMatchers, Command } from '../../keyMatchers.js';
|
||||
|
||||
interface ScrollableProps {
|
||||
@@ -123,30 +115,6 @@ export const Scrollable: React.FC<ScrollableProps> = ({
|
||||
[scrollToBottom],
|
||||
);
|
||||
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
useMouse(
|
||||
(event: MouseEvent) => {
|
||||
if (event.name === 'move' && viewportRef.current) {
|
||||
const boundingBox = getBoundingBox(viewportRef.current);
|
||||
if (boundingBox) {
|
||||
const { x, y, width, height } = boundingBox;
|
||||
const inside =
|
||||
event.col >= x &&
|
||||
event.col < x + width + 1 &&
|
||||
event.row >= y &&
|
||||
event.row < y + height;
|
||||
|
||||
if (inside !== isHovered) {
|
||||
setIsHovered(inside);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
{ isActive: true },
|
||||
);
|
||||
|
||||
const { getScrollTop, setPendingScrollTop } = useBatchedScroll(scrollTop);
|
||||
|
||||
const scrollBy = useCallback(
|
||||
@@ -167,20 +135,8 @@ export const Scrollable: React.FC<ScrollableProps> = ({
|
||||
const { scrollbarColor, flashScrollbar, scrollByWithAnimation } =
|
||||
useAnimatedScrollbar(hasFocus, scrollBy);
|
||||
|
||||
// Flash scrollbar on hover for discoverability.
|
||||
const wasHovered = useRef(isHovered);
|
||||
useEffect(() => {
|
||||
if (isHovered && !wasHovered.current && !hasFocus) {
|
||||
flashScrollbar();
|
||||
}
|
||||
wasHovered.current = isHovered;
|
||||
}, [isHovered, hasFocus, flashScrollbar]);
|
||||
|
||||
useKeypress(
|
||||
(key: Key) => {
|
||||
if (!hasFocus) {
|
||||
return false;
|
||||
}
|
||||
const { scrollHeight, innerHeight } = sizeRef.current;
|
||||
const scrollTop = getScrollTop();
|
||||
const maxScroll = Math.max(0, scrollHeight - innerHeight);
|
||||
@@ -219,20 +175,13 @@ export const Scrollable: React.FC<ScrollableProps> = ({
|
||||
);
|
||||
|
||||
const getScrollState = useCallback(() => {
|
||||
if (!hasFocus && !isHovered) {
|
||||
return {
|
||||
scrollTop: 0,
|
||||
scrollHeight: 0,
|
||||
innerHeight: 0,
|
||||
};
|
||||
}
|
||||
const maxScroll = Math.max(0, size.scrollHeight - size.innerHeight);
|
||||
return {
|
||||
scrollTop: Math.min(getScrollTop(), maxScroll),
|
||||
scrollHeight: size.scrollHeight,
|
||||
innerHeight: size.innerHeight,
|
||||
};
|
||||
}, [hasFocus, isHovered, getScrollTop, size.scrollHeight, size.innerHeight]);
|
||||
}, [getScrollTop, size.scrollHeight, size.innerHeight]);
|
||||
|
||||
const hasFocusCallback = useCallback(() => hasFocus, [hasFocus]);
|
||||
|
||||
@@ -257,11 +206,11 @@ export const Scrollable: React.FC<ScrollableProps> = ({
|
||||
width={width ?? maxWidth}
|
||||
height={height}
|
||||
flexDirection="column"
|
||||
overflowY={hasFocus || isHovered ? 'scroll' : 'hidden'}
|
||||
overflowY="scroll"
|
||||
overflowX="hidden"
|
||||
scrollTop={scrollTop}
|
||||
flexGrow={flexGrow}
|
||||
scrollbarThumbColor={hasFocus || isHovered ? scrollbarColor : undefined}
|
||||
scrollbarThumbColor={scrollbarColor}
|
||||
>
|
||||
{/*
|
||||
This inner box is necessary to prevent the parent from shrinking
|
||||
|
||||
@@ -80,7 +80,6 @@ export interface UIActions {
|
||||
revealCleanUiDetailsTemporarily: (durationMs?: number) => void;
|
||||
handleWarning: (message: string) => void;
|
||||
setEmbeddedShellFocused: (value: boolean) => void;
|
||||
setActivePtyId: (pid: number | null) => void;
|
||||
dismissBackgroundShell: (pid: number) => void;
|
||||
setActiveBackgroundShellPid: (pid: number) => void;
|
||||
setIsBackgroundShellListOpen: (isOpen: boolean) => void;
|
||||
|
||||
@@ -152,13 +152,6 @@ export const useShellCommandProcessor = (
|
||||
[m],
|
||||
);
|
||||
|
||||
const setActivePtyId = useCallback(
|
||||
(pid: number | null) => {
|
||||
dispatch({ type: 'SET_ACTIVE_PTY', pid });
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
const toggleBackgroundShell = useCallback(() => {
|
||||
if (state.backgroundShells.size > 0) {
|
||||
const willBeVisible = !state.isBackgroundShellVisible;
|
||||
@@ -557,6 +550,5 @@ export const useShellCommandProcessor = (
|
||||
registerBackgroundShell,
|
||||
dismissBackgroundShell,
|
||||
backgroundShells: state.backgroundShells,
|
||||
setActivePtyId,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -204,8 +204,9 @@ export const useGeminiStream = (
|
||||
consumeUserHint?: () => string | null,
|
||||
) => {
|
||||
const [initError, setInitError] = useState<string | null>(null);
|
||||
const [modelRetryStatus, setModelRetryStatus] =
|
||||
useState<RetryAttemptPayload | null>(null);
|
||||
const [retryStatus, setRetryStatus] = useState<RetryAttemptPayload | null>(
|
||||
null,
|
||||
);
|
||||
const isLowErrorVerbosity = settings.merged.ui?.errorVerbosity !== 'full';
|
||||
const suppressedToolErrorCountRef = useRef(0);
|
||||
const suppressedToolErrorNoteShownRef = useRef(false);
|
||||
@@ -241,7 +242,7 @@ export const useGeminiStream = (
|
||||
|
||||
useEffect(() => {
|
||||
const handleRetryAttempt = (payload: RetryAttemptPayload) => {
|
||||
setModelRetryStatus(payload);
|
||||
setRetryStatus(payload);
|
||||
};
|
||||
coreEvents.on(CoreEvent.RetryAttempt, handleRetryAttempt);
|
||||
return () => {
|
||||
@@ -337,7 +338,6 @@ export const useGeminiStream = (
|
||||
registerBackgroundShell,
|
||||
dismissBackgroundShell,
|
||||
backgroundShells,
|
||||
setActivePtyId,
|
||||
} = useShellCommandProcessor(
|
||||
addItem,
|
||||
setPendingHistoryItem,
|
||||
@@ -564,7 +564,7 @@ export const useGeminiStream = (
|
||||
|
||||
useEffect(() => {
|
||||
if (!isResponding) {
|
||||
setModelRetryStatus(null);
|
||||
setRetryStatus(null);
|
||||
}
|
||||
}, [isResponding]);
|
||||
|
||||
@@ -844,7 +844,7 @@ export const useGeminiStream = (
|
||||
currentGeminiMessageBuffer: string,
|
||||
userMessageTimestamp: number,
|
||||
): string => {
|
||||
setModelRetryStatus(null);
|
||||
setRetryStatus(null);
|
||||
if (turnCancelledRef.current) {
|
||||
// Prevents additional output after a user initiated cancel.
|
||||
return '';
|
||||
@@ -1897,7 +1897,6 @@ export const useGeminiStream = (
|
||||
backgroundCurrentShell,
|
||||
backgroundShells,
|
||||
dismissBackgroundShell,
|
||||
retryStatus: modelRetryStatus,
|
||||
setActivePtyId,
|
||||
retryStatus,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -196,36 +196,6 @@ describe('useTerminalTheme', () => {
|
||||
expect(mockHandleThemeSelect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should switch theme even if terminal background report is identical to previousColor if current theme is mismatched', () => {
|
||||
// Background is dark at startup
|
||||
config.setTerminalBackground('#000000');
|
||||
vi.mocked(config.setTerminalBackground).mockClear();
|
||||
// But theme is light
|
||||
mockSettings.merged.ui.theme = 'default-light';
|
||||
|
||||
const refreshStatic = vi.fn();
|
||||
const { unmount } = renderHook(() =>
|
||||
useTerminalTheme(mockHandleThemeSelect, config, refreshStatic),
|
||||
);
|
||||
|
||||
const handler = mockSubscribe.mock.calls[0][0];
|
||||
|
||||
// Terminal reports the same dark background
|
||||
handler('rgb:0000/0000/0000');
|
||||
|
||||
expect(config.setTerminalBackground).not.toHaveBeenCalled();
|
||||
expect(themeManager.setTerminalBackground).not.toHaveBeenCalled();
|
||||
expect(refreshStatic).not.toHaveBeenCalled();
|
||||
// But it SHOULD select the dark theme because of the mismatch!
|
||||
expect(mockHandleThemeSelect).toHaveBeenCalledWith(
|
||||
'default',
|
||||
expect.anything(),
|
||||
);
|
||||
|
||||
mockSettings.merged.ui.theme = 'default';
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should not switch theme if autoThemeSwitching is disabled', () => {
|
||||
mockSettings.merged.ui.autoThemeSwitching = false;
|
||||
const { unmount } = renderHook(() =>
|
||||
|
||||
@@ -59,6 +59,14 @@ export function useTerminalTheme(
|
||||
if (!hexColor) return;
|
||||
|
||||
const previousColor = config.getTerminalBackground();
|
||||
|
||||
if (previousColor === hexColor) {
|
||||
return;
|
||||
}
|
||||
|
||||
config.setTerminalBackground(hexColor);
|
||||
themeManager.setTerminalBackground(hexColor);
|
||||
|
||||
const luminance = getLuminance(hexColor);
|
||||
const currentThemeName = settings.merged.ui.theme;
|
||||
|
||||
@@ -69,16 +77,6 @@ export function useTerminalTheme(
|
||||
DefaultLight.name,
|
||||
);
|
||||
|
||||
if (previousColor === hexColor) {
|
||||
if (newTheme) {
|
||||
void handleThemeSelect(newTheme, SettingScope.User);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
config.setTerminalBackground(hexColor);
|
||||
themeManager.setTerminalBackground(hexColor);
|
||||
|
||||
if (newTheme) {
|
||||
void handleThemeSelect(newTheme, SettingScope.User);
|
||||
} else {
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
type AnyToolInvocation,
|
||||
ROOT_SCHEDULER_ID,
|
||||
CoreToolCallStatus,
|
||||
type WaitingToolCall,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { createMockMessageBus } from '@google/gemini-cli-core/src/test-utils/mock-message-bus.js';
|
||||
|
||||
@@ -33,7 +32,6 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
Scheduler: vi.fn().mockImplementation(() => ({
|
||||
schedule: vi.fn().mockResolvedValue([]),
|
||||
cancelAll: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
})),
|
||||
};
|
||||
});
|
||||
@@ -343,9 +341,7 @@ describe('useToolScheduler', () => {
|
||||
const callSub = {
|
||||
...callRoot,
|
||||
request: { ...callRoot.request, callId: 'call-sub' },
|
||||
status: CoreToolCallStatus.AwaitingApproval as const, // Must be awaiting approval to be tracked
|
||||
schedulerId: 'subagent-1',
|
||||
confirmationDetails: { type: 'info', title: 'Confirm', prompt: 'Yes?' },
|
||||
};
|
||||
|
||||
// 1. Populate state with multiple schedulers
|
||||
@@ -364,13 +360,9 @@ describe('useToolScheduler', () => {
|
||||
});
|
||||
|
||||
const [toolCalls] = result.current;
|
||||
expect(toolCalls).toHaveLength(2);
|
||||
expect(
|
||||
toolCalls.find((t) => t.request.callId === 'call-root'),
|
||||
).toBeDefined();
|
||||
expect(
|
||||
toolCalls.find((t) => t.request.callId === 'call-sub'),
|
||||
).toBeDefined();
|
||||
expect(toolCalls).toHaveLength(1);
|
||||
expect(toolCalls[0].request.callId).toBe('call-root');
|
||||
expect(toolCalls[0].schedulerId).toBe(ROOT_SCHEDULER_ID);
|
||||
|
||||
// 2. Call setToolCallsForDisplay (e.g., simulate a manual update or clear)
|
||||
act(() => {
|
||||
@@ -382,11 +374,12 @@ describe('useToolScheduler', () => {
|
||||
|
||||
// 3. Verify that tools are still present and maintain their scheduler IDs
|
||||
const [toolCalls2] = result.current;
|
||||
expect(toolCalls2).toHaveLength(2);
|
||||
expect(toolCalls2.every((t) => t.responseSubmittedToGemini)).toBe(true);
|
||||
expect(toolCalls2).toHaveLength(1);
|
||||
expect(toolCalls2[0].responseSubmittedToGemini).toBe(true);
|
||||
expect(toolCalls2[0].schedulerId).toBe(ROOT_SCHEDULER_ID);
|
||||
});
|
||||
|
||||
it('ignores TOOL_CALLS_UPDATE from non-root schedulers when no tools await approval', () => {
|
||||
it('ignores TOOL_CALLS_UPDATE from non-root schedulers', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useToolScheduler(
|
||||
vi.fn().mockResolvedValue(undefined),
|
||||
@@ -417,125 +410,8 @@ describe('useToolScheduler', () => {
|
||||
} as ToolCallsUpdateMessage);
|
||||
});
|
||||
|
||||
expect(result.current[0]).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('allows TOOL_CALLS_UPDATE from non-root schedulers when tools are awaiting approval', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useToolScheduler(
|
||||
vi.fn().mockResolvedValue(undefined),
|
||||
mockConfig,
|
||||
() => undefined,
|
||||
),
|
||||
);
|
||||
|
||||
const subagentCall = {
|
||||
status: CoreToolCallStatus.AwaitingApproval as const,
|
||||
request: {
|
||||
callId: 'call-sub',
|
||||
name: 'test',
|
||||
args: {},
|
||||
isClientInitiated: false,
|
||||
prompt_id: 'p1',
|
||||
},
|
||||
tool: createMockTool(),
|
||||
invocation: createMockInvocation(),
|
||||
schedulerId: 'subagent-1',
|
||||
confirmationDetails: { type: 'info', title: 'Confirm', prompt: 'Yes?' },
|
||||
} as WaitingToolCall;
|
||||
|
||||
act(() => {
|
||||
void mockMessageBus.publish({
|
||||
type: MessageBusType.TOOL_CALLS_UPDATE,
|
||||
toolCalls: [subagentCall],
|
||||
schedulerId: 'subagent-1',
|
||||
} as ToolCallsUpdateMessage);
|
||||
});
|
||||
|
||||
const [toolCalls] = result.current;
|
||||
expect(toolCalls).toHaveLength(1);
|
||||
expect(toolCalls[0].request.callId).toBe('call-sub');
|
||||
expect(toolCalls[0].status).toBe(CoreToolCallStatus.AwaitingApproval);
|
||||
});
|
||||
|
||||
it('preserves subagent tools in the UI after they have been approved', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useToolScheduler(
|
||||
vi.fn().mockResolvedValue(undefined),
|
||||
mockConfig,
|
||||
() => undefined,
|
||||
),
|
||||
);
|
||||
|
||||
const subagentCall = {
|
||||
status: CoreToolCallStatus.AwaitingApproval as const,
|
||||
request: {
|
||||
callId: 'call-sub',
|
||||
name: 'test',
|
||||
args: {},
|
||||
isClientInitiated: false,
|
||||
prompt_id: 'p1',
|
||||
},
|
||||
tool: createMockTool(),
|
||||
invocation: createMockInvocation(),
|
||||
schedulerId: 'subagent-1',
|
||||
confirmationDetails: { type: 'info', title: 'Confirm', prompt: 'Yes?' },
|
||||
} as WaitingToolCall;
|
||||
|
||||
// 1. Initial approval request
|
||||
act(() => {
|
||||
void mockMessageBus.publish({
|
||||
type: MessageBusType.TOOL_CALLS_UPDATE,
|
||||
toolCalls: [subagentCall],
|
||||
schedulerId: 'subagent-1',
|
||||
} as ToolCallsUpdateMessage);
|
||||
});
|
||||
|
||||
expect(result.current[0]).toHaveLength(1);
|
||||
|
||||
// 2. Approved and executing
|
||||
const approvedCall = {
|
||||
...subagentCall,
|
||||
status: CoreToolCallStatus.Executing as const,
|
||||
} as unknown as ExecutingToolCall;
|
||||
|
||||
act(() => {
|
||||
void mockMessageBus.publish({
|
||||
type: MessageBusType.TOOL_CALLS_UPDATE,
|
||||
toolCalls: [approvedCall],
|
||||
schedulerId: 'subagent-1',
|
||||
} as ToolCallsUpdateMessage);
|
||||
});
|
||||
|
||||
expect(result.current[0]).toHaveLength(1);
|
||||
expect(result.current[0][0].status).toBe(CoreToolCallStatus.Executing);
|
||||
|
||||
// 3. New turn with a background tool (should NOT be shown)
|
||||
const backgroundTool = {
|
||||
status: CoreToolCallStatus.Executing as const,
|
||||
request: {
|
||||
callId: 'call-background',
|
||||
name: 'read_file',
|
||||
args: {},
|
||||
isClientInitiated: false,
|
||||
prompt_id: 'p1',
|
||||
},
|
||||
tool: createMockTool(),
|
||||
invocation: createMockInvocation(),
|
||||
schedulerId: 'subagent-1',
|
||||
} as ExecutingToolCall;
|
||||
|
||||
act(() => {
|
||||
void mockMessageBus.publish({
|
||||
type: MessageBusType.TOOL_CALLS_UPDATE,
|
||||
toolCalls: [backgroundTool],
|
||||
schedulerId: 'subagent-1',
|
||||
} as ToolCallsUpdateMessage);
|
||||
});
|
||||
|
||||
// The subagent list should now be empty because the previously approved tool
|
||||
// is gone from the current list, and the new tool doesn't need approval.
|
||||
expect(result.current[0]).toHaveLength(0);
|
||||
expect(toolCalls).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('adapts success/error status to executing when a tail call is present', () => {
|
||||
|
||||
@@ -115,42 +115,11 @@ export function useToolScheduler(
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (event: ToolCallsUpdateMessage) => {
|
||||
const isRoot = event.schedulerId === ROOT_SCHEDULER_ID;
|
||||
|
||||
setToolCallsMap((prev) => {
|
||||
const prevCalls = prev[event.schedulerId] ?? [];
|
||||
const prevCallIds = new Set(prevCalls.map((tc) => tc.request.callId));
|
||||
|
||||
// For non-root schedulers, we only show tool calls that:
|
||||
// 1. Are currently awaiting approval.
|
||||
// 2. Were previously shown (e.g., they are now executing or completed).
|
||||
// This prevents "thinking" tools (reads/searches) from flickering in the UI
|
||||
// unless they specifically required user interaction.
|
||||
const filteredToolCalls = isRoot
|
||||
? event.toolCalls
|
||||
: event.toolCalls.filter(
|
||||
(tc) =>
|
||||
tc.status === CoreToolCallStatus.AwaitingApproval ||
|
||||
prevCallIds.has(tc.request.callId),
|
||||
);
|
||||
|
||||
// If this is a subagent and we have no tools to show and weren't showing any,
|
||||
// we can skip the update entirely to avoid unnecessary re-renders.
|
||||
if (
|
||||
!isRoot &&
|
||||
filteredToolCalls.length === 0 &&
|
||||
prevCalls.length === 0
|
||||
) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
const adapted = internalAdaptToolCalls(filteredToolCalls, prevCalls);
|
||||
|
||||
return {
|
||||
...prev,
|
||||
[event.schedulerId]: adapted,
|
||||
};
|
||||
});
|
||||
// Only process updates for the root scheduler.
|
||||
// Subagent internal tools should not be displayed in the main tool list.
|
||||
if (event.schedulerId !== ROOT_SCHEDULER_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update output timer for UI spinners (Side Effect)
|
||||
const hasExecuting = event.toolCalls.some(
|
||||
@@ -165,6 +134,18 @@ export function useToolScheduler(
|
||||
if (hasExecuting) {
|
||||
setLastToolOutputTime(Date.now());
|
||||
}
|
||||
|
||||
setToolCallsMap((prev) => {
|
||||
const adapted = internalAdaptToolCalls(
|
||||
event.toolCalls,
|
||||
prev[event.schedulerId] ?? [],
|
||||
);
|
||||
|
||||
return {
|
||||
...prev,
|
||||
[event.schedulerId]: adapted,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
messageBus.subscribe(MessageBusType.TOOL_CALLS_UPDATE, handler);
|
||||
|
||||
@@ -56,8 +56,7 @@ const validCustomTheme: CustomTheme = {
|
||||
|
||||
describe('ThemeManager', () => {
|
||||
beforeEach(() => {
|
||||
// Reset themeManager state and inject mocks
|
||||
themeManager.reinitialize({ fs, homedir: os.homedir });
|
||||
// Reset themeManager state
|
||||
themeManager.loadCustomThemes({});
|
||||
themeManager.setActiveTheme(DEFAULT_THEME.name);
|
||||
themeManager.setTerminalBackground(undefined);
|
||||
|
||||
@@ -61,13 +61,7 @@ class ThemeManager {
|
||||
private cachedSemanticColors: SemanticColors | undefined;
|
||||
private lastCacheKey: string | undefined;
|
||||
|
||||
private fs: typeof fs;
|
||||
private homedir: () => string;
|
||||
|
||||
constructor(dependencies?: { fs?: typeof fs; homedir?: () => string }) {
|
||||
this.fs = dependencies?.fs ?? fs;
|
||||
this.homedir = dependencies?.homedir ?? homedir;
|
||||
|
||||
constructor() {
|
||||
this.availableThemes = [
|
||||
AyuDark,
|
||||
AyuLight,
|
||||
@@ -248,44 +242,10 @@ class ThemeManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all themes loaded from files.
|
||||
* This is primarily for testing purposes to reset state between tests.
|
||||
* Sets the active theme.
|
||||
* @param themeName The name of the theme to set as active.
|
||||
* @returns True if the theme was successfully set, false otherwise.
|
||||
*/
|
||||
clearFileThemes(): void {
|
||||
this.fileThemes.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-initializes the ThemeManager with new dependencies.
|
||||
* This is primarily for testing to allow injecting mocks.
|
||||
*/
|
||||
reinitialize(dependencies: { fs?: typeof fs; homedir?: () => string }): void {
|
||||
if (dependencies.fs) {
|
||||
this.fs = dependencies.fs;
|
||||
}
|
||||
if (dependencies.homedir) {
|
||||
this.homedir = dependencies.homedir;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the ThemeManager state to defaults.
|
||||
* This is for testing purposes to ensure test isolation.
|
||||
*/
|
||||
resetForTesting(dependencies?: {
|
||||
fs?: typeof fs;
|
||||
homedir?: () => string;
|
||||
}): void {
|
||||
if (dependencies) {
|
||||
this.reinitialize(dependencies);
|
||||
}
|
||||
this.settingsThemes.clear();
|
||||
this.extensionThemes.clear();
|
||||
this.fileThemes.clear();
|
||||
this.activeTheme = DEFAULT_THEME;
|
||||
this.terminalBackground = undefined;
|
||||
this.clearCache();
|
||||
}
|
||||
setActiveTheme(themeName: string | undefined): boolean {
|
||||
const theme = this.findThemeByName(themeName);
|
||||
if (!theme) {
|
||||
@@ -545,7 +505,7 @@ class ThemeManager {
|
||||
private loadThemeFromFile(themePath: string): Theme | undefined {
|
||||
try {
|
||||
// realpathSync resolves the path and throws if it doesn't exist.
|
||||
const canonicalPath = this.fs.realpathSync(path.resolve(themePath));
|
||||
const canonicalPath = fs.realpathSync(path.resolve(themePath));
|
||||
|
||||
// 1. Check cache using the canonical path.
|
||||
if (this.fileThemes.has(canonicalPath)) {
|
||||
@@ -553,7 +513,7 @@ class ThemeManager {
|
||||
}
|
||||
|
||||
// 2. Perform security check.
|
||||
const homeDir = path.resolve(this.homedir());
|
||||
const homeDir = path.resolve(homedir());
|
||||
if (!canonicalPath.startsWith(homeDir)) {
|
||||
debugLogger.warn(
|
||||
`Theme file at "${themePath}" is outside your home directory. ` +
|
||||
@@ -563,7 +523,7 @@ class ThemeManager {
|
||||
}
|
||||
|
||||
// 3. Read, parse, and validate the theme file.
|
||||
const themeContent = this.fs.readFileSync(canonicalPath, 'utf-8');
|
||||
const themeContent = fs.readFileSync(canonicalPath, 'utf-8');
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
const customThemeConfig = JSON.parse(themeContent) as CustomTheme;
|
||||
|
||||
|
||||
@@ -5,17 +5,36 @@
|
||||
*/
|
||||
|
||||
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { spawn, exec, execSync } from 'node:child_process';
|
||||
import { spawn, execSync, type ChildProcess } from 'node:child_process';
|
||||
import os from 'node:os';
|
||||
import fs from 'node:fs';
|
||||
import { start_sandbox } from './sandbox.js';
|
||||
import { FatalSandboxError, type SandboxConfig } from '@google/gemini-cli-core';
|
||||
import {
|
||||
FatalSandboxError,
|
||||
type SandboxConfig,
|
||||
SandboxOrchestrator,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { EventEmitter } from 'node:events';
|
||||
|
||||
const { mockedHomedir, mockedGetContainerPath } = vi.hoisted(() => ({
|
||||
mockedHomedir: vi.fn().mockReturnValue('/home/user'),
|
||||
mockedGetContainerPath: vi.fn().mockImplementation((p: string) => p),
|
||||
}));
|
||||
const { mockedHomedir, mockedGetContainerPath, mockSpawnAsync } = vi.hoisted(
|
||||
() => ({
|
||||
mockedHomedir: vi.fn().mockReturnValue('/home/user'),
|
||||
mockedGetContainerPath: vi.fn().mockImplementation((p: string) => p),
|
||||
mockSpawnAsync: vi.fn().mockImplementation(async (cmd, args) => {
|
||||
if (cmd === 'id' && args?.[0] === '-u')
|
||||
return { stdout: '1000', stderr: '' };
|
||||
if (cmd === 'id' && args?.[0] === '-g')
|
||||
return { stdout: '1000', stderr: '' };
|
||||
if (cmd === 'getconf') return { stdout: '/tmp/cache', stderr: '' };
|
||||
if (cmd === 'docker' && args?.[0] === 'ps')
|
||||
return { stdout: 'existing-container', stderr: '' };
|
||||
if (cmd === 'docker' && args?.[0] === 'network')
|
||||
return { stdout: '', stderr: '' };
|
||||
if (cmd === 'curl') return { stdout: 'ok', stderr: '' };
|
||||
return { stdout: '', stderr: '' };
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
vi.mock('./sandboxUtils.js', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('./sandboxUtils.js')>();
|
||||
@@ -28,32 +47,6 @@ vi.mock('./sandboxUtils.js', async (importOriginal) => {
|
||||
vi.mock('node:child_process');
|
||||
vi.mock('node:os');
|
||||
vi.mock('node:fs');
|
||||
vi.mock('node:util', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('node:util')>();
|
||||
return {
|
||||
...actual,
|
||||
promisify: (fn: (...args: unknown[]) => unknown) => {
|
||||
if (fn === exec) {
|
||||
return async (cmd: string) => {
|
||||
if (cmd === 'id -u' || cmd === 'id -g') {
|
||||
return { stdout: '1000', stderr: '' };
|
||||
}
|
||||
if (cmd.includes('curl')) {
|
||||
return { stdout: '', stderr: '' };
|
||||
}
|
||||
if (cmd.includes('getconf DARWIN_USER_CACHE_DIR')) {
|
||||
return { stdout: '/tmp/cache', stderr: '' };
|
||||
}
|
||||
if (cmd.includes('ps -a --format')) {
|
||||
return { stdout: 'existing-container', stderr: '' };
|
||||
}
|
||||
return { stdout: '', stderr: '' };
|
||||
};
|
||||
}
|
||||
return actual.promisify(fn);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
const actual =
|
||||
@@ -68,13 +61,17 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
coreEvents: {
|
||||
emitFeedback: vi.fn(),
|
||||
},
|
||||
FatalSandboxError: class extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'FatalSandboxError';
|
||||
}
|
||||
SandboxOrchestrator: {
|
||||
ensureSandboxImageIsPresent: vi.fn().mockResolvedValue(true),
|
||||
getContainerRunArgs: vi
|
||||
.fn()
|
||||
.mockResolvedValue(['run', '-i', '--rm', '--init']),
|
||||
getSeatbeltArgs: vi.fn().mockReturnValue(['-D', 'TARGET_DIR=/tmp']),
|
||||
},
|
||||
GEMINI_DIR: '.gemini',
|
||||
spawnAsync: mockSpawnAsync,
|
||||
LOCAL_DEV_SANDBOX_IMAGE_NAME: 'gemini-cli-sandbox',
|
||||
SANDBOX_NETWORK_NAME: 'gemini-cli-sandbox',
|
||||
SANDBOX_PROXY_NAME: 'gemini-cli-sandbox-proxy',
|
||||
homedir: mockedHomedir,
|
||||
};
|
||||
});
|
||||
@@ -107,9 +104,27 @@ describe('sandbox', () => {
|
||||
vi.mocked(fs.existsSync).mockReturnValue(true);
|
||||
vi.mocked(fs.realpathSync).mockImplementation((p) => p as string);
|
||||
vi.mocked(execSync).mockReturnValue(Buffer.from(''));
|
||||
|
||||
// Default mockSpawnAsync implementation
|
||||
mockSpawnAsync.mockImplementation(async (cmd, args) => {
|
||||
if (cmd === 'id' && args?.[0] === '-u')
|
||||
return { stdout: '1000', stderr: '' };
|
||||
if (cmd === 'id' && args?.[0] === '-g')
|
||||
return { stdout: '1000', stderr: '' };
|
||||
if (cmd === 'getconf') return { stdout: '/tmp/cache', stderr: '' };
|
||||
if (cmd === 'docker' && args?.[0] === 'ps')
|
||||
return { stdout: 'existing-container', stderr: '' };
|
||||
if (cmd === 'docker' && args?.[0] === 'network')
|
||||
return { stdout: '', stderr: '' };
|
||||
if (cmd === 'curl') return { stdout: 'ok', stderr: '' };
|
||||
return { stdout: '', stderr: '' };
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Note: We intentionally avoid vi.restoreAllMocks() here because it clears the top-level
|
||||
// vi.mock('@google/gemini-cli-core') entirely, making its un-exported constants (like GEMINI_DIR)
|
||||
// undefined in subsequent tests. Call counts are still reset by clearAllMocks() in beforeEach.
|
||||
process.env = originalEnv;
|
||||
process.argv = originalArgv;
|
||||
});
|
||||
@@ -122,30 +137,27 @@ describe('sandbox', () => {
|
||||
image: 'some-image',
|
||||
};
|
||||
|
||||
interface MockProcess extends EventEmitter {
|
||||
stdout: EventEmitter;
|
||||
stderr: EventEmitter;
|
||||
}
|
||||
const mockSpawnProcess = new EventEmitter() as MockProcess;
|
||||
const mockSpawnProcess = new EventEmitter() as unknown as ChildProcess;
|
||||
// @ts-expect-error - mocking readonly property
|
||||
mockSpawnProcess.stdout = new EventEmitter();
|
||||
// @ts-expect-error - mocking readonly property
|
||||
mockSpawnProcess.stderr = new EventEmitter();
|
||||
// @ts-expect-error - mocking readonly property
|
||||
mockSpawnProcess.pid = 123;
|
||||
vi.mocked(spawn).mockReturnValue(
|
||||
mockSpawnProcess as unknown as ReturnType<typeof spawn>,
|
||||
);
|
||||
|
||||
const promise = start_sandbox(config, [], undefined, ['arg1']);
|
||||
|
||||
setTimeout(() => {
|
||||
mockSpawnProcess.emit('close', 0);
|
||||
}, 10);
|
||||
// Use setImmediate to ensure the promise has had a chance to register handlers
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
mockSpawnProcess.emit('close', 0);
|
||||
|
||||
await expect(promise).resolves.toBe(0);
|
||||
expect(spawn).toHaveBeenCalledWith(
|
||||
'sandbox-exec',
|
||||
expect.arrayContaining([
|
||||
'-f',
|
||||
expect.stringContaining('sandbox-macos-permissive-open.sb'),
|
||||
]),
|
||||
expect.arrayContaining(['-D', expect.stringContaining('TARGET_DIR=')]),
|
||||
expect.objectContaining({ stdio: 'inherit' }),
|
||||
);
|
||||
});
|
||||
@@ -167,152 +179,155 @@ describe('sandbox', () => {
|
||||
image: 'gemini-cli-sandbox',
|
||||
};
|
||||
|
||||
// Mock image check to return true (image exists)
|
||||
interface MockProcessWithStdout extends EventEmitter {
|
||||
stdout: EventEmitter;
|
||||
}
|
||||
const mockImageCheckProcess = new EventEmitter() as MockProcessWithStdout;
|
||||
mockImageCheckProcess.stdout = new EventEmitter();
|
||||
vi.mocked(spawn).mockImplementationOnce((_cmd, args) => {
|
||||
if (args && args[0] === 'images') {
|
||||
setTimeout(() => {
|
||||
mockImageCheckProcess.stdout.emit('data', Buffer.from('image-id'));
|
||||
mockImageCheckProcess.emit('close', 0);
|
||||
}, 1);
|
||||
return mockImageCheckProcess as unknown as ReturnType<typeof spawn>;
|
||||
}
|
||||
return new EventEmitter() as unknown as ReturnType<typeof spawn>; // fallback
|
||||
});
|
||||
|
||||
const mockSpawnProcess = new EventEmitter() as unknown as ReturnType<
|
||||
typeof spawn
|
||||
>;
|
||||
const mockSpawnProcess = new EventEmitter() as unknown as ChildProcess;
|
||||
// @ts-expect-error - mocking readonly property
|
||||
mockSpawnProcess.stdout = new EventEmitter();
|
||||
// @ts-expect-error - mocking readonly property
|
||||
mockSpawnProcess.stderr = new EventEmitter();
|
||||
// @ts-expect-error - mocking readonly property
|
||||
mockSpawnProcess.pid = 123;
|
||||
mockSpawnProcess.on = vi.fn().mockImplementation((event, cb) => {
|
||||
if (event === 'close') {
|
||||
setTimeout(() => cb(0), 10);
|
||||
setImmediate(() => cb(0));
|
||||
}
|
||||
return mockSpawnProcess;
|
||||
});
|
||||
vi.mocked(spawn).mockImplementationOnce((cmd, args) => {
|
||||
if (cmd === 'docker' && args && args[0] === 'run') {
|
||||
return mockSpawnProcess;
|
||||
}
|
||||
return new EventEmitter() as unknown as ReturnType<typeof spawn>;
|
||||
});
|
||||
vi.mocked(spawn).mockReturnValue(mockSpawnProcess);
|
||||
|
||||
const promise = start_sandbox(config, [], undefined, ['arg1']);
|
||||
|
||||
await expect(promise).resolves.toBe(0);
|
||||
expect(
|
||||
SandboxOrchestrator.ensureSandboxImageIsPresent,
|
||||
).toHaveBeenCalled();
|
||||
expect(SandboxOrchestrator.getContainerRunArgs).toHaveBeenCalled();
|
||||
expect(spawn).toHaveBeenCalledWith(
|
||||
'docker',
|
||||
expect.arrayContaining(['run', '-i', '--rm', '--init']),
|
||||
expect.any(Array),
|
||||
expect.objectContaining({ stdio: 'inherit' }),
|
||||
);
|
||||
});
|
||||
|
||||
it('should pull image if missing', async () => {
|
||||
it('should inject custom flags from SANDBOX_FLAGS env var', async () => {
|
||||
process.env['SANDBOX_FLAGS'] =
|
||||
'--security-opt label=disable --env FOO=bar';
|
||||
const config: SandboxConfig = {
|
||||
command: 'docker',
|
||||
image: 'missing-image',
|
||||
image: 'gemini-cli-sandbox',
|
||||
};
|
||||
|
||||
// 1. Image check fails
|
||||
interface MockProcessWithStdout extends EventEmitter {
|
||||
stdout: EventEmitter;
|
||||
}
|
||||
const mockImageCheckProcess1 =
|
||||
new EventEmitter() as MockProcessWithStdout;
|
||||
mockImageCheckProcess1.stdout = new EventEmitter();
|
||||
vi.mocked(spawn).mockImplementationOnce(() => {
|
||||
setTimeout(() => {
|
||||
mockImageCheckProcess1.emit('close', 0);
|
||||
}, 1);
|
||||
return mockImageCheckProcess1 as unknown as ReturnType<typeof spawn>;
|
||||
});
|
||||
|
||||
// 2. Pull image succeeds
|
||||
interface MockProcessWithStdoutStderr extends EventEmitter {
|
||||
stdout: EventEmitter;
|
||||
stderr: EventEmitter;
|
||||
}
|
||||
const mockPullProcess = new EventEmitter() as MockProcessWithStdoutStderr;
|
||||
mockPullProcess.stdout = new EventEmitter();
|
||||
mockPullProcess.stderr = new EventEmitter();
|
||||
vi.mocked(spawn).mockImplementationOnce(() => {
|
||||
setTimeout(() => {
|
||||
mockPullProcess.emit('close', 0);
|
||||
}, 1);
|
||||
return mockPullProcess as unknown as ReturnType<typeof spawn>;
|
||||
});
|
||||
|
||||
// 3. Image check succeeds
|
||||
const mockImageCheckProcess2 =
|
||||
new EventEmitter() as MockProcessWithStdout;
|
||||
mockImageCheckProcess2.stdout = new EventEmitter();
|
||||
vi.mocked(spawn).mockImplementationOnce(() => {
|
||||
setTimeout(() => {
|
||||
mockImageCheckProcess2.stdout.emit('data', Buffer.from('image-id'));
|
||||
mockImageCheckProcess2.emit('close', 0);
|
||||
}, 1);
|
||||
return mockImageCheckProcess2 as unknown as ReturnType<typeof spawn>;
|
||||
});
|
||||
|
||||
// 4. Docker run
|
||||
const mockSpawnProcess = new EventEmitter() as unknown as ReturnType<
|
||||
typeof spawn
|
||||
>;
|
||||
const mockSpawnProcess = new EventEmitter() as unknown as ChildProcess;
|
||||
mockSpawnProcess.on = vi.fn().mockImplementation((event, cb) => {
|
||||
if (event === 'close') {
|
||||
setTimeout(() => cb(0), 10);
|
||||
setImmediate(() => cb(0));
|
||||
}
|
||||
return mockSpawnProcess;
|
||||
});
|
||||
vi.mocked(spawn).mockImplementationOnce(() => mockSpawnProcess);
|
||||
vi.mocked(spawn).mockReturnValue(mockSpawnProcess);
|
||||
|
||||
const promise = start_sandbox(config, [], undefined, ['arg1']);
|
||||
await start_sandbox(config);
|
||||
|
||||
await expect(promise).resolves.toBe(0);
|
||||
expect(spawn).toHaveBeenCalledWith(
|
||||
'docker',
|
||||
expect.arrayContaining(['pull', 'missing-image']),
|
||||
expect.any(Object),
|
||||
expect(SandboxOrchestrator.getContainerRunArgs).toHaveBeenCalledWith(
|
||||
config,
|
||||
expect.any(String),
|
||||
expect.any(String),
|
||||
'--security-opt label=disable --env FOO=bar',
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw if image pull fails', async () => {
|
||||
it('should inject custom flags from config (settings)', async () => {
|
||||
const config: SandboxConfig = {
|
||||
command: 'docker',
|
||||
image: 'gemini-cli-sandbox',
|
||||
flags: '--privileged',
|
||||
};
|
||||
|
||||
const mockSpawnProcess = new EventEmitter() as unknown as ChildProcess;
|
||||
mockSpawnProcess.on = vi.fn().mockImplementation((event, cb) => {
|
||||
if (event === 'close') {
|
||||
setImmediate(() => cb(0));
|
||||
}
|
||||
return mockSpawnProcess;
|
||||
});
|
||||
vi.mocked(spawn).mockReturnValue(mockSpawnProcess);
|
||||
|
||||
await start_sandbox(config);
|
||||
|
||||
expect(SandboxOrchestrator.getContainerRunArgs).toHaveBeenCalledWith(
|
||||
config,
|
||||
expect.any(String),
|
||||
expect.any(String),
|
||||
undefined,
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('should expand multiple environment variables in sandbox flags', async () => {
|
||||
process.env['VAR1'] = 'val1';
|
||||
process.env['VAR2'] = 'val2';
|
||||
const config: SandboxConfig = {
|
||||
command: 'docker',
|
||||
image: 'gemini-cli-sandbox',
|
||||
flags: '--env V1=$VAR1 --env V2=${VAR2}',
|
||||
};
|
||||
|
||||
const mockSpawnProcess = new EventEmitter() as unknown as ChildProcess;
|
||||
mockSpawnProcess.on = vi.fn().mockImplementation((event, cb) => {
|
||||
if (event === 'close') {
|
||||
setImmediate(() => cb(0));
|
||||
}
|
||||
return mockSpawnProcess;
|
||||
});
|
||||
vi.mocked(spawn).mockReturnValue(mockSpawnProcess);
|
||||
|
||||
await start_sandbox(config);
|
||||
|
||||
expect(SandboxOrchestrator.getContainerRunArgs).toHaveBeenCalledWith(
|
||||
config,
|
||||
expect.any(String),
|
||||
expect.any(String),
|
||||
undefined,
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle quoted strings in sandbox flags', async () => {
|
||||
const config: SandboxConfig = {
|
||||
command: 'docker',
|
||||
image: 'gemini-cli-sandbox',
|
||||
flags: '--label "description=multi word label" --env \'FOO=bar baz\'',
|
||||
};
|
||||
|
||||
const mockSpawnProcess = new EventEmitter() as unknown as ChildProcess;
|
||||
mockSpawnProcess.on = vi.fn().mockImplementation((event, cb) => {
|
||||
if (event === 'close') {
|
||||
setImmediate(() => cb(0));
|
||||
}
|
||||
return mockSpawnProcess;
|
||||
});
|
||||
vi.mocked(spawn).mockReturnValue(mockSpawnProcess);
|
||||
|
||||
await start_sandbox(config);
|
||||
|
||||
expect(SandboxOrchestrator.getContainerRunArgs).toHaveBeenCalledWith(
|
||||
config,
|
||||
expect.any(String),
|
||||
expect.any(String),
|
||||
undefined,
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw if image is missing', async () => {
|
||||
const config: SandboxConfig = {
|
||||
command: 'docker',
|
||||
image: 'missing-image',
|
||||
};
|
||||
|
||||
// 1. Image check fails
|
||||
interface MockProcessWithStdout extends EventEmitter {
|
||||
stdout: EventEmitter;
|
||||
}
|
||||
const mockImageCheckProcess1 =
|
||||
new EventEmitter() as MockProcessWithStdout;
|
||||
mockImageCheckProcess1.stdout = new EventEmitter();
|
||||
vi.mocked(spawn).mockImplementationOnce(() => {
|
||||
setTimeout(() => {
|
||||
mockImageCheckProcess1.emit('close', 0);
|
||||
}, 1);
|
||||
return mockImageCheckProcess1 as unknown as ReturnType<typeof spawn>;
|
||||
});
|
||||
|
||||
// 2. Pull image fails
|
||||
interface MockProcessWithStdoutStderr extends EventEmitter {
|
||||
stdout: EventEmitter;
|
||||
stderr: EventEmitter;
|
||||
}
|
||||
const mockPullProcess = new EventEmitter() as MockProcessWithStdoutStderr;
|
||||
mockPullProcess.stdout = new EventEmitter();
|
||||
mockPullProcess.stderr = new EventEmitter();
|
||||
vi.mocked(spawn).mockImplementationOnce(() => {
|
||||
setTimeout(() => {
|
||||
mockPullProcess.emit('close', 1);
|
||||
}, 1);
|
||||
return mockPullProcess as unknown as ReturnType<typeof spawn>;
|
||||
});
|
||||
vi.mocked(
|
||||
SandboxOrchestrator.ensureSandboxImageIsPresent,
|
||||
).mockResolvedValueOnce(false);
|
||||
|
||||
await expect(start_sandbox(config)).rejects.toThrow(FatalSandboxError);
|
||||
});
|
||||
@@ -325,51 +340,20 @@ describe('sandbox', () => {
|
||||
process.env['SANDBOX_MOUNTS'] = '/host/path:/container/path:ro';
|
||||
vi.mocked(fs.existsSync).mockReturnValue(true); // For mount path check
|
||||
|
||||
// Mock image check to return true
|
||||
interface MockProcessWithStdout extends EventEmitter {
|
||||
stdout: EventEmitter;
|
||||
}
|
||||
const mockImageCheckProcess = new EventEmitter() as MockProcessWithStdout;
|
||||
mockImageCheckProcess.stdout = new EventEmitter();
|
||||
vi.mocked(spawn).mockImplementationOnce(() => {
|
||||
setTimeout(() => {
|
||||
mockImageCheckProcess.stdout.emit('data', Buffer.from('image-id'));
|
||||
mockImageCheckProcess.emit('close', 0);
|
||||
}, 1);
|
||||
return mockImageCheckProcess as unknown as ReturnType<typeof spawn>;
|
||||
});
|
||||
|
||||
const mockSpawnProcess = new EventEmitter() as unknown as ReturnType<
|
||||
typeof spawn
|
||||
>;
|
||||
const mockSpawnProcess = new EventEmitter() as unknown as ChildProcess;
|
||||
mockSpawnProcess.on = vi.fn().mockImplementation((event, cb) => {
|
||||
if (event === 'close') {
|
||||
setTimeout(() => cb(0), 10);
|
||||
setImmediate(() => cb(0));
|
||||
}
|
||||
return mockSpawnProcess;
|
||||
});
|
||||
vi.mocked(spawn).mockImplementationOnce(() => mockSpawnProcess);
|
||||
vi.mocked(spawn).mockReturnValue(mockSpawnProcess);
|
||||
|
||||
await start_sandbox(config);
|
||||
|
||||
// The first call is 'docker images -q ...'
|
||||
expect(spawn).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect(spawn).toHaveBeenCalledWith(
|
||||
'docker',
|
||||
expect.arrayContaining(['images', '-q']),
|
||||
);
|
||||
|
||||
// The second call is 'docker run ...'
|
||||
expect(spawn).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'docker',
|
||||
expect.arrayContaining([
|
||||
'run',
|
||||
'--volume',
|
||||
'/host/path:/container/path:ro',
|
||||
'--volume',
|
||||
expect.stringMatching(/[\\/]home[\\/]user[\\/]\.gemini/),
|
||||
]),
|
||||
expect.arrayContaining(['--volume', '/host/path:/container/path:ro']),
|
||||
expect.any(Object),
|
||||
);
|
||||
});
|
||||
@@ -382,30 +366,14 @@ describe('sandbox', () => {
|
||||
process.env['GOOGLE_GEMINI_BASE_URL'] = 'http://gemini.proxy';
|
||||
process.env['GOOGLE_VERTEX_BASE_URL'] = 'http://vertex.proxy';
|
||||
|
||||
// Mock image check to return true
|
||||
interface MockProcessWithStdout extends EventEmitter {
|
||||
stdout: EventEmitter;
|
||||
}
|
||||
const mockImageCheckProcess = new EventEmitter() as MockProcessWithStdout;
|
||||
mockImageCheckProcess.stdout = new EventEmitter();
|
||||
vi.mocked(spawn).mockImplementationOnce(() => {
|
||||
setTimeout(() => {
|
||||
mockImageCheckProcess.stdout.emit('data', Buffer.from('image-id'));
|
||||
mockImageCheckProcess.emit('close', 0);
|
||||
}, 1);
|
||||
return mockImageCheckProcess as unknown as ReturnType<typeof spawn>;
|
||||
});
|
||||
|
||||
const mockSpawnProcess = new EventEmitter() as unknown as ReturnType<
|
||||
typeof spawn
|
||||
>;
|
||||
const mockSpawnProcess = new EventEmitter() as unknown as ChildProcess;
|
||||
mockSpawnProcess.on = vi.fn().mockImplementation((event, cb) => {
|
||||
if (event === 'close') {
|
||||
setTimeout(() => cb(0), 10);
|
||||
setImmediate(() => cb(0));
|
||||
}
|
||||
return mockSpawnProcess;
|
||||
});
|
||||
vi.mocked(spawn).mockImplementationOnce(() => mockSpawnProcess);
|
||||
vi.mocked(spawn).mockReturnValue(mockSpawnProcess);
|
||||
|
||||
await start_sandbox(config);
|
||||
|
||||
@@ -434,30 +402,14 @@ describe('sandbox', () => {
|
||||
return Buffer.from('');
|
||||
});
|
||||
|
||||
// Mock image check to return true
|
||||
interface MockProcessWithStdout extends EventEmitter {
|
||||
stdout: EventEmitter;
|
||||
}
|
||||
const mockImageCheckProcess = new EventEmitter() as MockProcessWithStdout;
|
||||
mockImageCheckProcess.stdout = new EventEmitter();
|
||||
vi.mocked(spawn).mockImplementationOnce(() => {
|
||||
setTimeout(() => {
|
||||
mockImageCheckProcess.stdout.emit('data', Buffer.from('image-id'));
|
||||
mockImageCheckProcess.emit('close', 0);
|
||||
}, 1);
|
||||
return mockImageCheckProcess as unknown as ReturnType<typeof spawn>;
|
||||
});
|
||||
|
||||
const mockSpawnProcess = new EventEmitter() as unknown as ReturnType<
|
||||
typeof spawn
|
||||
>;
|
||||
const mockSpawnProcess = new EventEmitter() as unknown as ChildProcess;
|
||||
mockSpawnProcess.on = vi.fn().mockImplementation((event, cb) => {
|
||||
if (event === 'close') {
|
||||
setTimeout(() => cb(0), 10);
|
||||
setImmediate(() => cb(0));
|
||||
}
|
||||
return mockSpawnProcess;
|
||||
});
|
||||
vi.mocked(spawn).mockImplementationOnce(() => mockSpawnProcess);
|
||||
vi.mocked(spawn).mockReturnValue(mockSpawnProcess);
|
||||
|
||||
await start_sandbox(config);
|
||||
|
||||
@@ -467,11 +419,63 @@ describe('sandbox', () => {
|
||||
expect.any(Object),
|
||||
);
|
||||
// Check that the entrypoint command includes useradd/groupadd
|
||||
const args = vi.mocked(spawn).mock.calls[1][1] as string[];
|
||||
const args = vi.mocked(spawn).mock.calls[0][1] as string[];
|
||||
const entrypointCmd = args[args.length - 1];
|
||||
expect(entrypointCmd).toContain('groupadd');
|
||||
expect(entrypointCmd).toContain('useradd');
|
||||
expect(entrypointCmd).toContain('su -p gemini');
|
||||
});
|
||||
|
||||
describe('waitForProxy timeout', () => {
|
||||
it('should time out waiting for proxy', async () => {
|
||||
const config: SandboxConfig = {
|
||||
command: 'docker',
|
||||
image: 'gemini-cli-sandbox',
|
||||
};
|
||||
process.env['GEMINI_SANDBOX_PROXY_COMMAND'] = 'my-proxy';
|
||||
|
||||
// Mock spawn to return processes that stay open
|
||||
vi.mocked(spawn).mockImplementation(() => {
|
||||
const p = new EventEmitter() as unknown as ChildProcess;
|
||||
// @ts-expect-error - mocking readonly property
|
||||
p.pid = 123;
|
||||
p.kill = vi.fn();
|
||||
// @ts-expect-error - mocking readonly property
|
||||
p.stderr = new EventEmitter();
|
||||
// @ts-expect-error - mocking readonly property
|
||||
p.stdout = new EventEmitter();
|
||||
return p;
|
||||
});
|
||||
|
||||
// Mock spawnAsync to fail for curl (simulating proxy not started)
|
||||
mockSpawnAsync.mockImplementation(async (cmd) => {
|
||||
if (cmd === 'curl') {
|
||||
throw new Error('Connection refused');
|
||||
}
|
||||
return { stdout: '', stderr: '' };
|
||||
});
|
||||
|
||||
// Mock Date.now to control time
|
||||
let currentTime = 1000000;
|
||||
const dateSpy = vi.spyOn(Date, 'now').mockImplementation(() => {
|
||||
currentTime += 10000; // Increment time by 10s on each call to hit timeout fast
|
||||
return currentTime;
|
||||
});
|
||||
|
||||
// We also need to mock setTimeout to resolve immediately,
|
||||
// otherwise the loop will still take real time.
|
||||
const originalSetTimeout = global.setTimeout;
|
||||
// @ts-expect-error - mocking global setTimeout
|
||||
global.setTimeout = vi.fn().mockImplementation((cb) => cb());
|
||||
|
||||
try {
|
||||
const promise = start_sandbox(config);
|
||||
await expect(promise).rejects.toThrow(/Timed out waiting for proxy/);
|
||||
} finally {
|
||||
dateSpy.mockRestore();
|
||||
global.setTimeout = originalSetTimeout;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+117
-220
@@ -1,16 +1,20 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { exec, execSync, spawn, type ChildProcess } from 'node:child_process';
|
||||
import {
|
||||
execSync,
|
||||
spawn,
|
||||
spawnSync,
|
||||
type ChildProcess,
|
||||
} from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { quote, parse } from 'shell-quote';
|
||||
import { promisify } from 'node:util';
|
||||
import { quote } from 'shell-quote';
|
||||
import type { Config, SandboxConfig } from '@google/gemini-cli-core';
|
||||
import {
|
||||
coreEvents,
|
||||
@@ -18,6 +22,11 @@ import {
|
||||
FatalSandboxError,
|
||||
GEMINI_DIR,
|
||||
homedir,
|
||||
SandboxOrchestrator,
|
||||
LOCAL_DEV_SANDBOX_IMAGE_NAME,
|
||||
SANDBOX_NETWORK_NAME,
|
||||
SANDBOX_PROXY_NAME,
|
||||
spawnAsync,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { ConsolePatcher } from '../ui/utils/ConsolePatcher.js';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
@@ -27,13 +36,30 @@ import {
|
||||
parseImageName,
|
||||
ports,
|
||||
entrypoint,
|
||||
LOCAL_DEV_SANDBOX_IMAGE_NAME,
|
||||
SANDBOX_NETWORK_NAME,
|
||||
SANDBOX_PROXY_NAME,
|
||||
BUILTIN_SEATBELT_PROFILES,
|
||||
} from './sandboxUtils.js';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
async function waitForProxy(
|
||||
proxyUrl: string,
|
||||
timeoutMs: number = 30000,
|
||||
retryDelayMs: number = 500,
|
||||
now: () => number = Date.now,
|
||||
): Promise<void> {
|
||||
const start = now();
|
||||
while (now() - start < timeoutMs) {
|
||||
try {
|
||||
await spawnAsync('curl', ['-s', proxyUrl], {
|
||||
timeout: 500,
|
||||
});
|
||||
return;
|
||||
} catch {
|
||||
await new Promise((r) => setTimeout(r, retryDelayMs));
|
||||
}
|
||||
}
|
||||
throw new FatalSandboxError(
|
||||
`Timed out waiting for proxy at ${proxyUrl} to start after ${timeoutMs / 1000} seconds`,
|
||||
);
|
||||
}
|
||||
|
||||
export async function start_sandbox(
|
||||
config: SandboxConfig,
|
||||
@@ -70,26 +96,16 @@ export async function start_sandbox(
|
||||
);
|
||||
}
|
||||
debugLogger.log(`using macos seatbelt (profile: ${profile}) ...`);
|
||||
// if DEBUG is set, convert to --inspect-brk in NODE_OPTIONS
|
||||
const nodeOptions = [
|
||||
...(process.env['DEBUG'] ? ['--inspect-brk'] : []),
|
||||
...nodeArgs,
|
||||
].join(' ');
|
||||
const cacheDir = (
|
||||
await spawnAsync('getconf', ['DARWIN_USER_CACHE_DIR'])
|
||||
).stdout.trim();
|
||||
|
||||
const args = [
|
||||
'-D',
|
||||
`TARGET_DIR=${fs.realpathSync(process.cwd())}`,
|
||||
'-D',
|
||||
`TMP_DIR=${fs.realpathSync(os.tmpdir())}`,
|
||||
'-D',
|
||||
`HOME_DIR=${fs.realpathSync(homedir())}`,
|
||||
'-D',
|
||||
`CACHE_DIR=${fs.realpathSync((await execAsync('getconf DARWIN_USER_CACHE_DIR')).stdout.trim())}`,
|
||||
];
|
||||
const targetDirReal = fs.realpathSync(process.cwd());
|
||||
const tmpDirReal = fs.realpathSync(os.tmpdir());
|
||||
const homeDirReal = fs.realpathSync(homedir());
|
||||
const cacheDirReal = fs.realpathSync(cacheDir);
|
||||
|
||||
// Add included directories from the workspace context
|
||||
// Always add 5 INCLUDE_DIR parameters to ensure .sb files can reference them
|
||||
const MAX_INCLUDE_DIRS = 5;
|
||||
const targetDir = fs.realpathSync(cliConfig?.getTargetDir() || '');
|
||||
const includedDirs: string[] = [];
|
||||
|
||||
@@ -106,21 +122,24 @@ export async function start_sandbox(
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < MAX_INCLUDE_DIRS; i++) {
|
||||
let dirPath = '/dev/null'; // Default to a safe path that won't cause issues
|
||||
const args = SandboxOrchestrator.getSeatbeltArgs(
|
||||
targetDirReal,
|
||||
tmpDirReal,
|
||||
homeDirReal,
|
||||
cacheDirReal,
|
||||
profileFile,
|
||||
includedDirs,
|
||||
);
|
||||
|
||||
if (i < includedDirs.length) {
|
||||
dirPath = includedDirs[i];
|
||||
}
|
||||
|
||||
args.push('-D', `INCLUDE_DIR_${i}=${dirPath}`);
|
||||
}
|
||||
// if DEBUG is set, convert to --inspect-brk in NODE_OPTIONS
|
||||
const nodeOptions = [
|
||||
...(process.env['DEBUG'] ? ['--inspect-brk'] : []),
|
||||
...nodeArgs,
|
||||
].join(' ');
|
||||
|
||||
const finalArgv = cliArgs;
|
||||
|
||||
args.push(
|
||||
'-f',
|
||||
profileFile,
|
||||
'sh',
|
||||
'-c',
|
||||
[
|
||||
@@ -161,6 +180,7 @@ export async function start_sandbox(
|
||||
if (proxyProcess?.pid) {
|
||||
process.kill(-proxyProcess.pid, 'SIGTERM');
|
||||
}
|
||||
return;
|
||||
};
|
||||
process.off('exit', stopProxy);
|
||||
process.on('exit', stopProxy);
|
||||
@@ -185,9 +205,7 @@ export async function start_sandbox(
|
||||
);
|
||||
});
|
||||
debugLogger.log('waiting for proxy to start ...');
|
||||
await execAsync(
|
||||
`until timeout 0.25 curl -s http://localhost:8877; do sleep 0.25; done`,
|
||||
);
|
||||
await waitForProxy('http://localhost:8877', 30000, 500);
|
||||
}
|
||||
// spawn child and let it inherit stdio
|
||||
process.stdin.pause();
|
||||
@@ -255,7 +273,11 @@ export async function start_sandbox(
|
||||
|
||||
// stop if image is missing
|
||||
if (
|
||||
!(await ensureSandboxImageIsPresent(config.command, image, cliConfig))
|
||||
!(await SandboxOrchestrator.ensureSandboxImageIsPresent(
|
||||
config.command,
|
||||
image,
|
||||
cliConfig,
|
||||
))
|
||||
) {
|
||||
const remedy =
|
||||
image === LOCAL_DEV_SANDBOX_IMAGE_NAME
|
||||
@@ -268,26 +290,13 @@ export async function start_sandbox(
|
||||
|
||||
// use interactive mode and auto-remove container on exit
|
||||
// run init binary inside container to forward signals & reap zombies
|
||||
const args = ['run', '-i', '--rm', '--init', '--workdir', containerWorkdir];
|
||||
|
||||
// add custom flags from SANDBOX_FLAGS
|
||||
if (process.env['SANDBOX_FLAGS']) {
|
||||
const flags = parse(process.env['SANDBOX_FLAGS'], process.env).filter(
|
||||
(f): f is string => typeof f === 'string',
|
||||
);
|
||||
args.push(...flags);
|
||||
}
|
||||
|
||||
// add TTY only if stdin is TTY as well, i.e. for piped input don't init TTY in container
|
||||
if (process.stdin.isTTY) {
|
||||
args.push('-t');
|
||||
}
|
||||
|
||||
// allow access to host.docker.internal
|
||||
args.push('--add-host', 'host.docker.internal:host-gateway');
|
||||
|
||||
// mount current directory as working directory in sandbox (set via --workdir)
|
||||
args.push('--volume', `${workdir}:${containerWorkdir}`);
|
||||
const args = await SandboxOrchestrator.getContainerRunArgs(
|
||||
config,
|
||||
workdir,
|
||||
containerWorkdir,
|
||||
process.env['SANDBOX_FLAGS'],
|
||||
!process.stdin.isTTY,
|
||||
);
|
||||
|
||||
// mount user settings directory inside container, after creating if missing
|
||||
// note user/home changes inside sandbox and we mount at BOTH paths for consistency
|
||||
@@ -409,17 +418,38 @@ export async function start_sandbox(
|
||||
|
||||
// if using proxy, switch to internal networking through proxy
|
||||
if (proxy) {
|
||||
execSync(
|
||||
`${config.command} network inspect ${SANDBOX_NETWORK_NAME} || ${config.command} network create --internal ${SANDBOX_NETWORK_NAME}`,
|
||||
);
|
||||
try {
|
||||
await spawnAsync(config.command, [
|
||||
'network',
|
||||
'inspect',
|
||||
SANDBOX_NETWORK_NAME,
|
||||
]);
|
||||
} catch {
|
||||
await spawnAsync(config.command, [
|
||||
'network',
|
||||
'create',
|
||||
'--internal',
|
||||
SANDBOX_NETWORK_NAME,
|
||||
]);
|
||||
}
|
||||
args.push('--network', SANDBOX_NETWORK_NAME);
|
||||
// if proxy command is set, create a separate network w/ host access (i.e. non-internal)
|
||||
// we will run proxy in its own container connected to both host network and internal network
|
||||
// this allows proxy to work even on rootless podman on macos with host<->vm<->container isolation
|
||||
if (proxyCommand) {
|
||||
execSync(
|
||||
`${config.command} network inspect ${SANDBOX_PROXY_NAME} || ${config.command} network create ${SANDBOX_PROXY_NAME}`,
|
||||
);
|
||||
try {
|
||||
await spawnAsync(config.command, [
|
||||
'network',
|
||||
'inspect',
|
||||
SANDBOX_PROXY_NAME,
|
||||
]);
|
||||
} catch {
|
||||
await spawnAsync(config.command, [
|
||||
'network',
|
||||
'create',
|
||||
SANDBOX_PROXY_NAME,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -436,9 +466,12 @@ export async function start_sandbox(
|
||||
debugLogger.log(`ContainerName: ${containerName}`);
|
||||
} else {
|
||||
let index = 0;
|
||||
const containerNameCheck = (
|
||||
await execAsync(`${config.command} ps -a --format "{{.Names}}"`)
|
||||
).stdout.trim();
|
||||
const { stdout: containerNameCheck } = await spawnAsync(config.command, [
|
||||
'ps',
|
||||
'-a',
|
||||
'--format',
|
||||
'{{.Names}}',
|
||||
]);
|
||||
while (containerNameCheck.includes(`${imageName}-${index}`)) {
|
||||
index++;
|
||||
}
|
||||
@@ -606,8 +639,8 @@ export async function start_sandbox(
|
||||
// The entrypoint script then handles dropping privileges to the correct user.
|
||||
args.push('--user', 'root');
|
||||
|
||||
const uid = (await execAsync('id -u')).stdout.trim();
|
||||
const gid = (await execAsync('id -g')).stdout.trim();
|
||||
const uid = (await spawnAsync('id', ['-u'])).stdout.trim();
|
||||
const gid = (await spawnAsync('id', ['-g'])).stdout.trim();
|
||||
|
||||
// Instead of passing --user to the main sandbox container, we let it
|
||||
// start as root, then create a user with the host's UID/GID, and
|
||||
@@ -660,8 +693,13 @@ export async function start_sandbox(
|
||||
// install handlers to stop proxy on exit/signal
|
||||
const stopProxy = () => {
|
||||
debugLogger.log('stopping proxy container ...');
|
||||
execSync(`${config.command} rm -f ${SANDBOX_PROXY_NAME}`);
|
||||
try {
|
||||
spawnSync(config.command, ['rm', '-f', SANDBOX_PROXY_NAME]);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
|
||||
process.off('exit', stopProxy);
|
||||
process.on('exit', stopProxy);
|
||||
process.off('SIGINT', stopProxy);
|
||||
@@ -681,18 +719,19 @@ export async function start_sandbox(
|
||||
process.kill(-sandboxProcess.pid, 'SIGTERM');
|
||||
}
|
||||
throw new FatalSandboxError(
|
||||
`Proxy container command '${proxyContainerCommand}' exited with code ${code}, signal ${signal}`,
|
||||
`Proxy container command exited with code ${code}, signal ${signal}`,
|
||||
);
|
||||
});
|
||||
debugLogger.log('waiting for proxy to start ...');
|
||||
await execAsync(
|
||||
`until timeout 0.25 curl -s http://localhost:8877; do sleep 0.25; done`,
|
||||
);
|
||||
await waitForProxy('http://localhost:8877', 30000, 500);
|
||||
// connect proxy container to sandbox network
|
||||
// (workaround for older versions of docker that don't support multiple --network args)
|
||||
await execAsync(
|
||||
`${config.command} network connect ${SANDBOX_NETWORK_NAME} ${SANDBOX_PROXY_NAME}`,
|
||||
);
|
||||
await spawnAsync(config.command, [
|
||||
'network',
|
||||
'connect',
|
||||
SANDBOX_NETWORK_NAME,
|
||||
SANDBOX_PROXY_NAME,
|
||||
]);
|
||||
}
|
||||
|
||||
// spawn child and let it inherit stdio
|
||||
@@ -721,145 +760,3 @@ export async function start_sandbox(
|
||||
patcher.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions to ensure sandbox image is present
|
||||
async function imageExists(sandbox: string, image: string): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const args = ['images', '-q', image];
|
||||
const checkProcess = spawn(sandbox, args);
|
||||
|
||||
let stdoutData = '';
|
||||
if (checkProcess.stdout) {
|
||||
checkProcess.stdout.on('data', (data) => {
|
||||
stdoutData += data.toString();
|
||||
});
|
||||
}
|
||||
|
||||
checkProcess.on('error', (err) => {
|
||||
debugLogger.warn(
|
||||
`Failed to start '${sandbox}' command for image check: ${err.message}`,
|
||||
);
|
||||
resolve(false);
|
||||
});
|
||||
|
||||
checkProcess.on('close', (code) => {
|
||||
// Non-zero code might indicate docker daemon not running, etc.
|
||||
// The primary success indicator is non-empty stdoutData.
|
||||
if (code !== 0) {
|
||||
// console.warn(`'${sandbox} images -q ${image}' exited with code ${code}.`);
|
||||
}
|
||||
resolve(stdoutData.trim() !== '');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function pullImage(
|
||||
sandbox: string,
|
||||
image: string,
|
||||
cliConfig?: Config,
|
||||
): Promise<boolean> {
|
||||
debugLogger.debug(`Attempting to pull image ${image} using ${sandbox}...`);
|
||||
return new Promise((resolve) => {
|
||||
const args = ['pull', image];
|
||||
const pullProcess = spawn(sandbox, args, { stdio: 'pipe' });
|
||||
|
||||
let stderrData = '';
|
||||
|
||||
const onStdoutData = (data: Buffer) => {
|
||||
if (cliConfig?.getDebugMode() || process.env['DEBUG']) {
|
||||
debugLogger.log(data.toString().trim()); // Show pull progress
|
||||
}
|
||||
};
|
||||
|
||||
const onStderrData = (data: Buffer) => {
|
||||
stderrData += data.toString();
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(data.toString().trim()); // Show pull errors/info from the command itself
|
||||
};
|
||||
|
||||
const onError = (err: Error) => {
|
||||
debugLogger.warn(
|
||||
`Failed to start '${sandbox} pull ${image}' command: ${err.message}`,
|
||||
);
|
||||
cleanup();
|
||||
resolve(false);
|
||||
};
|
||||
|
||||
const onClose = (code: number | null) => {
|
||||
if (code === 0) {
|
||||
debugLogger.log(`Successfully pulled image ${image}.`);
|
||||
cleanup();
|
||||
resolve(true);
|
||||
} else {
|
||||
debugLogger.warn(
|
||||
`Failed to pull image ${image}. '${sandbox} pull ${image}' exited with code ${code}.`,
|
||||
);
|
||||
if (stderrData.trim()) {
|
||||
// Details already printed by the stderr listener above
|
||||
}
|
||||
cleanup();
|
||||
resolve(false);
|
||||
}
|
||||
};
|
||||
|
||||
const cleanup = () => {
|
||||
if (pullProcess.stdout) {
|
||||
pullProcess.stdout.removeListener('data', onStdoutData);
|
||||
}
|
||||
if (pullProcess.stderr) {
|
||||
pullProcess.stderr.removeListener('data', onStderrData);
|
||||
}
|
||||
pullProcess.removeListener('error', onError);
|
||||
pullProcess.removeListener('close', onClose);
|
||||
if (pullProcess.connected) {
|
||||
pullProcess.disconnect();
|
||||
}
|
||||
};
|
||||
|
||||
if (pullProcess.stdout) {
|
||||
pullProcess.stdout.on('data', onStdoutData);
|
||||
}
|
||||
if (pullProcess.stderr) {
|
||||
pullProcess.stderr.on('data', onStderrData);
|
||||
}
|
||||
pullProcess.on('error', onError);
|
||||
pullProcess.on('close', onClose);
|
||||
});
|
||||
}
|
||||
|
||||
async function ensureSandboxImageIsPresent(
|
||||
sandbox: string,
|
||||
image: string,
|
||||
cliConfig?: Config,
|
||||
): Promise<boolean> {
|
||||
debugLogger.log(`Checking for sandbox image: ${image}`);
|
||||
if (await imageExists(sandbox, image)) {
|
||||
debugLogger.log(`Sandbox image ${image} found locally.`);
|
||||
return true;
|
||||
}
|
||||
|
||||
debugLogger.log(`Sandbox image ${image} not found locally.`);
|
||||
if (image === LOCAL_DEV_SANDBOX_IMAGE_NAME) {
|
||||
// user needs to build the image themselves
|
||||
return false;
|
||||
}
|
||||
|
||||
if (await pullImage(sandbox, image, cliConfig)) {
|
||||
// After attempting to pull, check again to be certain
|
||||
if (await imageExists(sandbox, image)) {
|
||||
debugLogger.log(`Sandbox image ${image} is now available after pulling.`);
|
||||
return true;
|
||||
} else {
|
||||
debugLogger.warn(
|
||||
`Sandbox image ${image} still not found after a pull attempt. This might indicate an issue with the image name or registry, or the pull command reported success but failed to make the image available.`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
coreEvents.emitFeedback(
|
||||
'error',
|
||||
`Failed to obtain sandbox image ${image} after check and pull attempt.`,
|
||||
);
|
||||
return false; // Pull command failed or image still not present
|
||||
}
|
||||
|
||||
@@ -10,9 +10,6 @@ import { readFile } from 'node:fs/promises';
|
||||
import { quote } from 'shell-quote';
|
||||
import { debugLogger, GEMINI_DIR } from '@google/gemini-cli-core';
|
||||
|
||||
export const LOCAL_DEV_SANDBOX_IMAGE_NAME = 'gemini-cli-sandbox';
|
||||
export const SANDBOX_NETWORK_NAME = 'gemini-cli-sandbox';
|
||||
export const SANDBOX_PROXY_NAME = 'gemini-cli-sandbox-proxy';
|
||||
export const BUILTIN_SEATBELT_PROFILES = [
|
||||
'permissive-open',
|
||||
'permissive-proxied',
|
||||
|
||||
@@ -129,7 +129,6 @@ describe('GeminiAgent', () => {
|
||||
mockConfig = {
|
||||
refreshAuth: vi.fn(),
|
||||
initialize: vi.fn(),
|
||||
waitForMcpInit: vi.fn(),
|
||||
getFileSystemService: vi.fn(),
|
||||
setFileSystemService: vi.fn(),
|
||||
getContentGeneratorConfig: vi.fn(),
|
||||
@@ -487,7 +486,6 @@ describe('Session', () => {
|
||||
getMessageBus: vi.fn().mockReturnValue(mockMessageBus),
|
||||
setApprovalMode: vi.fn(),
|
||||
isPlanEnabled: vi.fn().mockReturnValue(false),
|
||||
waitForMcpInit: vi.fn(),
|
||||
} as unknown as Mocked<Config>;
|
||||
mockConnection = {
|
||||
sessionUpdate: vi.fn(),
|
||||
@@ -502,28 +500,6 @@ describe('Session', () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should await MCP initialization before processing a prompt', async () => {
|
||||
const stream = createMockStream([
|
||||
{
|
||||
type: StreamEventType.CHUNK,
|
||||
value: { candidates: [{ content: { parts: [{ text: 'Hi' }] } }] },
|
||||
},
|
||||
]);
|
||||
mockChat.sendMessageStream.mockResolvedValue(stream);
|
||||
|
||||
await session.prompt({
|
||||
sessionId: 'session-1',
|
||||
prompt: [{ type: 'text', text: 'test' }],
|
||||
});
|
||||
|
||||
expect(mockConfig.waitForMcpInit).toHaveBeenCalledOnce();
|
||||
const waitOrder = (mockConfig.waitForMcpInit as Mock).mock
|
||||
.invocationCallOrder[0];
|
||||
const sendOrder = (mockChat.sendMessageStream as Mock).mock
|
||||
.invocationCallOrder[0];
|
||||
expect(waitOrder).toBeLessThan(sendOrder);
|
||||
});
|
||||
|
||||
it('should handle prompt with text response', async () => {
|
||||
const stream = createMockStream([
|
||||
{
|
||||
@@ -649,133 +625,6 @@ describe('Session', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should use filePath for ACP diff content in permission request', async () => {
|
||||
const confirmationDetails = {
|
||||
type: 'edit',
|
||||
title: 'Confirm Write: test.txt',
|
||||
fileName: 'test.txt',
|
||||
filePath: '/tmp/test.txt',
|
||||
originalContent: 'old',
|
||||
newContent: 'new',
|
||||
onConfirm: vi.fn(),
|
||||
};
|
||||
mockTool.build.mockReturnValue({
|
||||
getDescription: () => 'Test Tool',
|
||||
toolLocations: () => [],
|
||||
shouldConfirmExecute: vi.fn().mockResolvedValue(confirmationDetails),
|
||||
execute: vi.fn().mockResolvedValue({ llmContent: 'Tool Result' }),
|
||||
});
|
||||
|
||||
mockConnection.requestPermission.mockResolvedValue({
|
||||
outcome: {
|
||||
outcome: 'selected',
|
||||
optionId: ToolConfirmationOutcome.ProceedOnce,
|
||||
},
|
||||
});
|
||||
|
||||
const stream1 = createMockStream([
|
||||
{
|
||||
type: StreamEventType.CHUNK,
|
||||
value: {
|
||||
functionCalls: [{ name: 'test_tool', args: {} }],
|
||||
},
|
||||
},
|
||||
]);
|
||||
const stream2 = createMockStream([
|
||||
{
|
||||
type: StreamEventType.CHUNK,
|
||||
value: { candidates: [] },
|
||||
},
|
||||
]);
|
||||
|
||||
mockChat.sendMessageStream
|
||||
.mockResolvedValueOnce(stream1)
|
||||
.mockResolvedValueOnce(stream2);
|
||||
|
||||
await session.prompt({
|
||||
sessionId: 'session-1',
|
||||
prompt: [{ type: 'text', text: 'Call tool' }],
|
||||
});
|
||||
|
||||
expect(mockConnection.requestPermission).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
toolCall: expect.objectContaining({
|
||||
content: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: 'diff',
|
||||
path: '/tmp/test.txt',
|
||||
oldText: 'old',
|
||||
newText: 'new',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should use filePath for ACP diff content in tool result', async () => {
|
||||
mockTool.build.mockReturnValue({
|
||||
getDescription: () => 'Test Tool',
|
||||
toolLocations: () => [],
|
||||
shouldConfirmExecute: vi.fn().mockResolvedValue(null),
|
||||
execute: vi.fn().mockResolvedValue({
|
||||
llmContent: 'Tool Result',
|
||||
returnDisplay: {
|
||||
fileName: 'test.txt',
|
||||
filePath: '/tmp/test.txt',
|
||||
originalContent: 'old',
|
||||
newContent: 'new',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const stream1 = createMockStream([
|
||||
{
|
||||
type: StreamEventType.CHUNK,
|
||||
value: {
|
||||
functionCalls: [{ name: 'test_tool', args: {} }],
|
||||
},
|
||||
},
|
||||
]);
|
||||
const stream2 = createMockStream([
|
||||
{
|
||||
type: StreamEventType.CHUNK,
|
||||
value: { candidates: [] },
|
||||
},
|
||||
]);
|
||||
|
||||
mockChat.sendMessageStream
|
||||
.mockResolvedValueOnce(stream1)
|
||||
.mockResolvedValueOnce(stream2);
|
||||
|
||||
await session.prompt({
|
||||
sessionId: 'session-1',
|
||||
prompt: [{ type: 'text', text: 'Call tool' }],
|
||||
});
|
||||
|
||||
const updateCalls = mockConnection.sessionUpdate.mock.calls.map(
|
||||
(call) => call[0],
|
||||
);
|
||||
const toolCallUpdate = updateCalls.find(
|
||||
(call) => call.update?.sessionUpdate === 'tool_call_update',
|
||||
);
|
||||
|
||||
expect(toolCallUpdate).toEqual(
|
||||
expect.objectContaining({
|
||||
update: expect.objectContaining({
|
||||
content: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: 'diff',
|
||||
path: '/tmp/test.txt',
|
||||
oldText: 'old',
|
||||
newText: 'new',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle tool call cancellation by user', async () => {
|
||||
const confirmationDetails = {
|
||||
type: 'info',
|
||||
|
||||
@@ -521,8 +521,6 @@ export class Session {
|
||||
const pendingSend = new AbortController();
|
||||
this.pendingPrompt = pendingSend;
|
||||
|
||||
await this.config.waitForMcpInit();
|
||||
|
||||
const promptId = Math.random().toString(16).slice(2);
|
||||
const chat = this.chat;
|
||||
|
||||
@@ -702,7 +700,7 @@ export class Session {
|
||||
if (confirmationDetails.type === 'edit') {
|
||||
content.push({
|
||||
type: 'diff',
|
||||
path: confirmationDetails.filePath,
|
||||
path: confirmationDetails.fileName,
|
||||
oldText: confirmationDetails.originalContent,
|
||||
newText: confirmationDetails.newContent,
|
||||
_meta: {
|
||||
@@ -1230,9 +1228,7 @@ function toToolCallContent(toolResult: ToolResult): acp.ToolCallContent | null {
|
||||
if ('fileName' in toolResult.returnDisplay) {
|
||||
return {
|
||||
type: 'diff',
|
||||
path:
|
||||
toolResult.returnDisplay.filePath ??
|
||||
toolResult.returnDisplay.fileName,
|
||||
path: toolResult.returnDisplay.fileName,
|
||||
oldText: toolResult.returnDisplay.originalContent,
|
||||
newText: toolResult.returnDisplay.newContent,
|
||||
_meta: {
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
import { vi, beforeEach, afterEach } from 'vitest';
|
||||
import { format } from 'node:util';
|
||||
import { coreEvents } from '@google/gemini-cli-core';
|
||||
import { themeManager } from './src/ui/themes/theme-manager.js';
|
||||
|
||||
// Unset CI environment variable so that ink renders dynamically as it does in a real terminal
|
||||
if (process.env.CI !== undefined) {
|
||||
@@ -33,9 +32,6 @@ let consoleErrorSpy: vi.SpyInstance;
|
||||
let actWarnings: Array<{ message: string; stack: string }> = [];
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset themeManager state to ensure test isolation
|
||||
themeManager.resetForTesting();
|
||||
|
||||
actWarnings = [];
|
||||
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation((...args) => {
|
||||
const firstArg = args[0];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli-core",
|
||||
"version": "0.33.0-nightly.20260228.1ca5c05d0",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"description": "Gemini CLI Core",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
|
||||
@@ -431,6 +431,7 @@ export enum AuthProviderType {
|
||||
export interface SandboxConfig {
|
||||
command: 'docker' | 'podman' | 'sandbox-exec';
|
||||
image: string;
|
||||
flags?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -716,7 +717,6 @@ export class Config implements McpContext {
|
||||
private compressionTruncationCounter = 0;
|
||||
private initialized = false;
|
||||
private initPromise: Promise<void> | undefined;
|
||||
private mcpInitializationPromise: Promise<void> | null = null;
|
||||
readonly storage: Storage;
|
||||
private readonly fileExclusions: FileExclusions;
|
||||
private readonly eventEmitter?: EventEmitter;
|
||||
@@ -1125,7 +1125,7 @@ export class Config implements McpContext {
|
||||
);
|
||||
// We do not await this promise so that the CLI can start up even if
|
||||
// MCP servers are slow to connect.
|
||||
this.mcpInitializationPromise = Promise.allSettled([
|
||||
const mcpInitialization = Promise.allSettled([
|
||||
this.mcpClientManager.startConfiguredMcpServers(),
|
||||
this.getExtensionLoader().start(this),
|
||||
]).then((results) => {
|
||||
@@ -1137,7 +1137,7 @@ export class Config implements McpContext {
|
||||
});
|
||||
|
||||
if (!this.interactive || this.experimentalZedIntegration) {
|
||||
await this.mcpInitializationPromise;
|
||||
await mcpInitialization;
|
||||
}
|
||||
|
||||
if (this.skillsSupport) {
|
||||
@@ -2235,12 +2235,6 @@ export class Config implements McpContext {
|
||||
return this.experimentalZedIntegration;
|
||||
}
|
||||
|
||||
async waitForMcpInit(): Promise<void> {
|
||||
if (this.mcpInitializationPromise) {
|
||||
await this.mcpInitializationPromise;
|
||||
}
|
||||
}
|
||||
|
||||
getListExtensions(): boolean {
|
||||
return this.listExtensions;
|
||||
}
|
||||
|
||||
@@ -243,78 +243,6 @@ describe('LoggingContentGenerator', () => {
|
||||
expect(errorEvent.error_type).toBe('FatalAuthenticationError');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Gaxios error parsing', () => {
|
||||
it('should parse raw ASCII buffer strings in Gaxios errors', async () => {
|
||||
const req = { contents: [], model: 'gemini-pro' };
|
||||
|
||||
// Simulate a Gaxios error with comma-separated ASCII codes
|
||||
const asciiData = '72,101,108,108,111'; // "Hello"
|
||||
const gaxiosError = Object.assign(new Error('Gaxios Error'), {
|
||||
response: { data: asciiData },
|
||||
});
|
||||
|
||||
vi.mocked(wrapped.generateContent).mockRejectedValue(gaxiosError);
|
||||
|
||||
await expect(
|
||||
loggingContentGenerator.generateContent(
|
||||
req,
|
||||
'prompt-123',
|
||||
LlmRole.MAIN,
|
||||
),
|
||||
).rejects.toSatisfy((error: unknown) => {
|
||||
const gError = error as { response: { data: unknown } };
|
||||
expect(gError.response.data).toBe('Hello');
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
it('should leave data alone if it is not a comma-separated string', async () => {
|
||||
const req = { contents: [], model: 'gemini-pro' };
|
||||
|
||||
const normalData = 'Normal error message';
|
||||
const gaxiosError = Object.assign(new Error('Gaxios Error'), {
|
||||
response: { data: normalData },
|
||||
});
|
||||
|
||||
vi.mocked(wrapped.generateContent).mockRejectedValue(gaxiosError);
|
||||
|
||||
await expect(
|
||||
loggingContentGenerator.generateContent(
|
||||
req,
|
||||
'prompt-123',
|
||||
LlmRole.MAIN,
|
||||
),
|
||||
).rejects.toSatisfy((error: unknown) => {
|
||||
const gError = error as { response: { data: unknown } };
|
||||
expect(gError.response.data).toBe(normalData);
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
it('should leave data alone if parsing fails', async () => {
|
||||
const req = { contents: [], model: 'gemini-pro' };
|
||||
|
||||
const invalidAscii = '72,invalid,101';
|
||||
const gaxiosError = Object.assign(new Error('Gaxios Error'), {
|
||||
response: { data: invalidAscii },
|
||||
});
|
||||
|
||||
vi.mocked(wrapped.generateContent).mockRejectedValue(gaxiosError);
|
||||
|
||||
await expect(
|
||||
loggingContentGenerator.generateContent(
|
||||
req,
|
||||
'prompt-123',
|
||||
LlmRole.MAIN,
|
||||
),
|
||||
).rejects.toSatisfy((error: unknown) => {
|
||||
const gError = error as { response: { data: unknown } };
|
||||
expect(gError.response.data).toBe(invalidAscii);
|
||||
return true;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateContentStream', () => {
|
||||
|
||||
@@ -274,32 +274,6 @@ export class LoggingContentGenerator implements ContentGenerator {
|
||||
logApiResponse(this.config, event);
|
||||
}
|
||||
|
||||
private _fixGaxiosErrorData(error: unknown): void {
|
||||
// Fix for raw ASCII buffer strings appearing in dev with the latest
|
||||
// Gaxios updates.
|
||||
if (
|
||||
typeof error === 'object' &&
|
||||
error !== null &&
|
||||
'response' in error &&
|
||||
typeof error.response === 'object' &&
|
||||
error.response !== null &&
|
||||
'data' in error.response
|
||||
) {
|
||||
const response = error.response as { data: unknown };
|
||||
const data = response.data;
|
||||
if (typeof data === 'string' && data.includes(',')) {
|
||||
try {
|
||||
const charCodes = data.split(',').map(Number);
|
||||
if (charCodes.every((code) => !isNaN(code))) {
|
||||
response.data = String.fromCharCode(...charCodes);
|
||||
}
|
||||
} catch (_e) {
|
||||
// If parsing fails, just leave it alone
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _logApiError(
|
||||
durationMs: number,
|
||||
error: unknown,
|
||||
@@ -406,9 +380,6 @@ export class LoggingContentGenerator implements ContentGenerator {
|
||||
} catch (error) {
|
||||
spanMetadata.error = error;
|
||||
const durationMs = Date.now() - startTime;
|
||||
|
||||
this._fixGaxiosErrorData(error);
|
||||
|
||||
this._logApiError(
|
||||
durationMs,
|
||||
error,
|
||||
@@ -476,9 +447,6 @@ export class LoggingContentGenerator implements ContentGenerator {
|
||||
);
|
||||
} catch (error) {
|
||||
const durationMs = Date.now() - startTime;
|
||||
|
||||
this._fixGaxiosErrorData(error);
|
||||
|
||||
this._logApiError(
|
||||
durationMs,
|
||||
error,
|
||||
|
||||
@@ -112,6 +112,7 @@ export * from './utils/apiConversionUtils.js';
|
||||
export * from './utils/channel.js';
|
||||
export * from './utils/constants.js';
|
||||
export * from './utils/sessionUtils.js';
|
||||
export * from './utils/sandboxOrchestrator.js';
|
||||
|
||||
// Export services
|
||||
export * from './services/fileDiscoveryService.js';
|
||||
@@ -188,7 +189,6 @@ export { OAuthUtils } from './mcp/oauth-utils.js';
|
||||
export * from './telemetry/index.js';
|
||||
export * from './telemetry/billingEvents.js';
|
||||
export { logBillingEvent } from './telemetry/loggers.js';
|
||||
export * from './telemetry/constants.js';
|
||||
export { sessionId, createSessionId } from './utils/session.js';
|
||||
export * from './utils/compatibility.js';
|
||||
export * from './utils/browser.js';
|
||||
|
||||
@@ -17,16 +17,6 @@ import {
|
||||
SHELL_TOOL_NAME,
|
||||
WRITE_FILE_TOOL_NAME,
|
||||
WRITE_TODOS_TOOL_NAME,
|
||||
GREP_PARAM_TOTAL_MAX_MATCHES,
|
||||
GREP_PARAM_INCLUDE_PATTERN,
|
||||
GREP_PARAM_EXCLUDE_PATTERN,
|
||||
GREP_PARAM_CONTEXT,
|
||||
GREP_PARAM_BEFORE,
|
||||
GREP_PARAM_AFTER,
|
||||
READ_FILE_PARAM_START_LINE,
|
||||
READ_FILE_PARAM_END_LINE,
|
||||
SHELL_PARAM_IS_BACKGROUND,
|
||||
EDIT_PARAM_OLD_STRING,
|
||||
} from '../tools/tool-names.js';
|
||||
import type { HierarchicalMemory } from '../config/memory.js';
|
||||
import { DEFAULT_CONTEXT_FILENAME } from '../tools/memoryTool.js';
|
||||
@@ -193,16 +183,16 @@ Use the following guidelines to optimize your search and read patterns.
|
||||
- Prefer using tools like ${GREP_TOOL_NAME} to identify points of interest instead of reading lots of files individually.
|
||||
- If you need to read multiple ranges in a file, do so parallel, in as few turns as possible.
|
||||
- It is more important to reduce extra turns, but please also try to minimize unnecessarily large file reads and search results, when doing so doesn't result in extra turns. Do this by always providing conservative limits and scopes to tools like ${READ_FILE_TOOL_NAME} and ${GREP_TOOL_NAME}.
|
||||
- ${READ_FILE_TOOL_NAME} fails if ${EDIT_PARAM_OLD_STRING} is ambiguous, causing extra turns. Take care to read enough with ${READ_FILE_TOOL_NAME} and ${GREP_TOOL_NAME} to make the edit unambiguous.
|
||||
- ${READ_FILE_TOOL_NAME} fails if old_string is ambiguous, causing extra turns. Take care to read enough with ${READ_FILE_TOOL_NAME} and ${GREP_TOOL_NAME} to make the edit unambiguous.
|
||||
- You can compensate for the risk of missing results with scoped or limited searches by doing multiple searches in parallel.
|
||||
- Your primary goal is still to do your best quality work. Efficiency is an important, but secondary concern.
|
||||
</guidelines>
|
||||
|
||||
<examples>
|
||||
- **Searching:** utilize search tools like ${GREP_TOOL_NAME} and ${GLOB_TOOL_NAME} with a conservative result count (\`${GREP_PARAM_TOTAL_MAX_MATCHES}\`) and a narrow scope (\`${GREP_PARAM_INCLUDE_PATTERN}\` and \`${GREP_PARAM_EXCLUDE_PATTERN}\` parameters).
|
||||
- **Searching and editing:** utilize search tools like ${GREP_TOOL_NAME} with a conservative result count and a narrow scope. Use \`${GREP_PARAM_CONTEXT}\`, \`${GREP_PARAM_BEFORE}\`, and/or \`${GREP_PARAM_AFTER}\` to request enough context to avoid the need to read the file before editing matches.
|
||||
- **Searching:** utilize search tools like ${GREP_TOOL_NAME} and ${GLOB_TOOL_NAME} with a conservative result count (\`total_max_matches\`) and a narrow scope (\`include_pattern\` and \`exclude_pattern\` parameters).
|
||||
- **Searching and editing:** utilize search tools like ${GREP_TOOL_NAME} with a conservative result count and a narrow scope. Use \`context\`, \`before\`, and/or \`after\` to request enough context to avoid the need to read the file before editing matches.
|
||||
- **Understanding:** minimize turns needed to understand a file. It's most efficient to read small files in their entirety.
|
||||
- **Large files:** utilize search tools like ${GREP_TOOL_NAME} and/or ${READ_FILE_TOOL_NAME} called in parallel with '${READ_FILE_PARAM_START_LINE}' and '${READ_FILE_PARAM_END_LINE}' to reduce the impact on context. Minimize extra turns, unless unavoidable due to the file being too large.
|
||||
- **Large files:** utilize search tools like ${GREP_TOOL_NAME} and/or ${READ_FILE_TOOL_NAME} called in parallel with 'start_line' and 'end_line' to reduce the impact on context. Minimize extra turns, unless unavoidable due to the file being too large.
|
||||
- **Navigating:** read the minimum required to not require additional turns spent reading the file.
|
||||
</examples>
|
||||
|
||||
@@ -669,11 +659,11 @@ function toolUsageInteractive(
|
||||
? ' If you choose to execute an interactive command consider letting the user know they can press `ctrl + f` to focus into the shell to provide input.'
|
||||
: '';
|
||||
return `
|
||||
- **Background Processes:** To run a command in the background, set the \`${SHELL_PARAM_IS_BACKGROUND}\` parameter to true. If unsure, ask the user.
|
||||
- **Background Processes:** To run a command in the background, set the \`is_background\` parameter to true. If unsure, ask the user.
|
||||
- **Interactive Commands:** Always prefer non-interactive commands (e.g., using 'run once' or 'CI' flags for test runners to avoid persistent watch modes or 'git --no-pager') unless a persistent process is specifically required; however, some commands are only interactive and expect user input during their execution (e.g. ssh, vim).${ctrlF}`;
|
||||
}
|
||||
return `
|
||||
- **Background Processes:** To run a command in the background, set the \`${SHELL_PARAM_IS_BACKGROUND}\` parameter to true.
|
||||
- **Background Processes:** To run a command in the background, set the \`is_background\` parameter to true.
|
||||
- **Interactive Commands:** Always prefer non-interactive commands (e.g., using 'run once' or 'CI' flags for test runners to avoid persistent watch modes or 'git --no-pager') unless a persistent process is specifically required; however, some commands are only interactive and expect user input during their execution (e.g. ssh, vim).`;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,115 +10,25 @@
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// SHARED PARAMETER NAMES (used by multiple tools)
|
||||
// TOOL NAMES
|
||||
// ============================================================================
|
||||
|
||||
export const PARAM_FILE_PATH = 'file_path';
|
||||
export const PARAM_DIR_PATH = 'dir_path';
|
||||
export const PARAM_PATTERN = 'pattern';
|
||||
export const PARAM_CASE_SENSITIVE = 'case_sensitive';
|
||||
export const PARAM_RESPECT_GIT_IGNORE = 'respect_git_ignore';
|
||||
export const PARAM_RESPECT_GEMINI_IGNORE = 'respect_gemini_ignore';
|
||||
export const PARAM_FILE_FILTERING_OPTIONS = 'file_filtering_options';
|
||||
export const PARAM_DESCRIPTION = 'description';
|
||||
|
||||
// ============================================================================
|
||||
// TOOL NAMES & TOOL-SPECIFIC PARAMETER NAMES
|
||||
// ============================================================================
|
||||
|
||||
// -- glob --
|
||||
export const GLOB_TOOL_NAME = 'glob';
|
||||
|
||||
// -- grep_search --
|
||||
export const GREP_TOOL_NAME = 'grep_search';
|
||||
export const GREP_PARAM_INCLUDE_PATTERN = 'include_pattern';
|
||||
export const GREP_PARAM_EXCLUDE_PATTERN = 'exclude_pattern';
|
||||
export const GREP_PARAM_NAMES_ONLY = 'names_only';
|
||||
export const GREP_PARAM_MAX_MATCHES_PER_FILE = 'max_matches_per_file';
|
||||
export const GREP_PARAM_TOTAL_MAX_MATCHES = 'total_max_matches';
|
||||
// ripgrep only
|
||||
export const GREP_PARAM_FIXED_STRINGS = 'fixed_strings';
|
||||
export const GREP_PARAM_CONTEXT = 'context';
|
||||
export const GREP_PARAM_AFTER = 'after';
|
||||
export const GREP_PARAM_BEFORE = 'before';
|
||||
export const GREP_PARAM_NO_IGNORE = 'no_ignore';
|
||||
|
||||
// -- list_directory --
|
||||
export const LS_TOOL_NAME = 'list_directory';
|
||||
export const LS_PARAM_IGNORE = 'ignore';
|
||||
|
||||
// -- read_file --
|
||||
export const READ_FILE_TOOL_NAME = 'read_file';
|
||||
export const READ_FILE_PARAM_START_LINE = 'start_line';
|
||||
export const READ_FILE_PARAM_END_LINE = 'end_line';
|
||||
|
||||
// -- run_shell_command --
|
||||
export const SHELL_TOOL_NAME = 'run_shell_command';
|
||||
export const SHELL_PARAM_COMMAND = 'command';
|
||||
export const SHELL_PARAM_IS_BACKGROUND = 'is_background';
|
||||
|
||||
// -- write_file --
|
||||
export const WRITE_FILE_TOOL_NAME = 'write_file';
|
||||
export const WRITE_FILE_PARAM_CONTENT = 'content';
|
||||
|
||||
// -- replace (edit) --
|
||||
export const EDIT_TOOL_NAME = 'replace';
|
||||
export const EDIT_PARAM_INSTRUCTION = 'instruction';
|
||||
export const EDIT_PARAM_OLD_STRING = 'old_string';
|
||||
export const EDIT_PARAM_NEW_STRING = 'new_string';
|
||||
export const EDIT_PARAM_ALLOW_MULTIPLE = 'allow_multiple';
|
||||
|
||||
// -- google_web_search --
|
||||
export const WEB_SEARCH_TOOL_NAME = 'google_web_search';
|
||||
export const WEB_SEARCH_PARAM_QUERY = 'query';
|
||||
|
||||
// -- write_todos --
|
||||
export const WRITE_TODOS_TOOL_NAME = 'write_todos';
|
||||
export const TODOS_PARAM_TODOS = 'todos';
|
||||
export const TODOS_ITEM_PARAM_DESCRIPTION = 'description';
|
||||
export const TODOS_ITEM_PARAM_STATUS = 'status';
|
||||
|
||||
// -- web_fetch --
|
||||
export const WEB_FETCH_TOOL_NAME = 'web_fetch';
|
||||
export const WEB_FETCH_PARAM_PROMPT = 'prompt';
|
||||
|
||||
// -- read_many_files --
|
||||
export const READ_MANY_FILES_TOOL_NAME = 'read_many_files';
|
||||
export const READ_MANY_PARAM_INCLUDE = 'include';
|
||||
export const READ_MANY_PARAM_EXCLUDE = 'exclude';
|
||||
export const READ_MANY_PARAM_RECURSIVE = 'recursive';
|
||||
export const READ_MANY_PARAM_USE_DEFAULT_EXCLUDES = 'useDefaultExcludes';
|
||||
|
||||
// -- save_memory --
|
||||
export const MEMORY_TOOL_NAME = 'save_memory';
|
||||
export const MEMORY_PARAM_FACT = 'fact';
|
||||
|
||||
// -- get_internal_docs --
|
||||
export const GET_INTERNAL_DOCS_TOOL_NAME = 'get_internal_docs';
|
||||
export const DOCS_PARAM_PATH = 'path';
|
||||
|
||||
// -- activate_skill --
|
||||
export const ACTIVATE_SKILL_TOOL_NAME = 'activate_skill';
|
||||
export const SKILL_PARAM_NAME = 'name';
|
||||
|
||||
// -- ask_user --
|
||||
export const ASK_USER_TOOL_NAME = 'ask_user';
|
||||
export const ASK_USER_PARAM_QUESTIONS = 'questions';
|
||||
// ask_user question item params
|
||||
export const ASK_USER_QUESTION_PARAM_QUESTION = 'question';
|
||||
export const ASK_USER_QUESTION_PARAM_HEADER = 'header';
|
||||
export const ASK_USER_QUESTION_PARAM_TYPE = 'type';
|
||||
export const ASK_USER_QUESTION_PARAM_OPTIONS = 'options';
|
||||
export const ASK_USER_QUESTION_PARAM_MULTI_SELECT = 'multiSelect';
|
||||
export const ASK_USER_QUESTION_PARAM_PLACEHOLDER = 'placeholder';
|
||||
// ask_user option item params
|
||||
export const ASK_USER_OPTION_PARAM_LABEL = 'label';
|
||||
export const ASK_USER_OPTION_PARAM_DESCRIPTION = 'description';
|
||||
|
||||
// -- exit_plan_mode --
|
||||
export const EXIT_PLAN_MODE_TOOL_NAME = 'exit_plan_mode';
|
||||
export const EXIT_PLAN_PARAM_PLAN_PATH = 'plan_path';
|
||||
|
||||
// -- enter_plan_mode --
|
||||
export const ENTER_PLAN_MODE_TOOL_NAME = 'enter_plan_mode';
|
||||
export const PLAN_MODE_PARAM_REASON = 'reason';
|
||||
|
||||
@@ -38,59 +38,6 @@ export {
|
||||
ASK_USER_TOOL_NAME,
|
||||
EXIT_PLAN_MODE_TOOL_NAME,
|
||||
ENTER_PLAN_MODE_TOOL_NAME,
|
||||
// Shared parameter names
|
||||
PARAM_FILE_PATH,
|
||||
PARAM_DIR_PATH,
|
||||
PARAM_PATTERN,
|
||||
PARAM_CASE_SENSITIVE,
|
||||
PARAM_RESPECT_GIT_IGNORE,
|
||||
PARAM_RESPECT_GEMINI_IGNORE,
|
||||
PARAM_FILE_FILTERING_OPTIONS,
|
||||
PARAM_DESCRIPTION,
|
||||
// Tool-specific parameter names
|
||||
READ_FILE_PARAM_START_LINE,
|
||||
READ_FILE_PARAM_END_LINE,
|
||||
WRITE_FILE_PARAM_CONTENT,
|
||||
GREP_PARAM_INCLUDE_PATTERN,
|
||||
GREP_PARAM_EXCLUDE_PATTERN,
|
||||
GREP_PARAM_NAMES_ONLY,
|
||||
GREP_PARAM_MAX_MATCHES_PER_FILE,
|
||||
GREP_PARAM_TOTAL_MAX_MATCHES,
|
||||
GREP_PARAM_FIXED_STRINGS,
|
||||
GREP_PARAM_CONTEXT,
|
||||
GREP_PARAM_AFTER,
|
||||
GREP_PARAM_BEFORE,
|
||||
GREP_PARAM_NO_IGNORE,
|
||||
EDIT_PARAM_INSTRUCTION,
|
||||
EDIT_PARAM_OLD_STRING,
|
||||
EDIT_PARAM_NEW_STRING,
|
||||
EDIT_PARAM_ALLOW_MULTIPLE,
|
||||
LS_PARAM_IGNORE,
|
||||
SHELL_PARAM_COMMAND,
|
||||
SHELL_PARAM_IS_BACKGROUND,
|
||||
WEB_SEARCH_PARAM_QUERY,
|
||||
WEB_FETCH_PARAM_PROMPT,
|
||||
READ_MANY_PARAM_INCLUDE,
|
||||
READ_MANY_PARAM_EXCLUDE,
|
||||
READ_MANY_PARAM_RECURSIVE,
|
||||
READ_MANY_PARAM_USE_DEFAULT_EXCLUDES,
|
||||
MEMORY_PARAM_FACT,
|
||||
TODOS_PARAM_TODOS,
|
||||
TODOS_ITEM_PARAM_DESCRIPTION,
|
||||
TODOS_ITEM_PARAM_STATUS,
|
||||
DOCS_PARAM_PATH,
|
||||
ASK_USER_PARAM_QUESTIONS,
|
||||
ASK_USER_QUESTION_PARAM_QUESTION,
|
||||
ASK_USER_QUESTION_PARAM_HEADER,
|
||||
ASK_USER_QUESTION_PARAM_TYPE,
|
||||
ASK_USER_QUESTION_PARAM_OPTIONS,
|
||||
ASK_USER_QUESTION_PARAM_MULTI_SELECT,
|
||||
ASK_USER_QUESTION_PARAM_PLACEHOLDER,
|
||||
ASK_USER_OPTION_PARAM_LABEL,
|
||||
ASK_USER_OPTION_PARAM_DESCRIPTION,
|
||||
PLAN_MODE_PARAM_REASON,
|
||||
EXIT_PLAN_PARAM_PLAN_PATH,
|
||||
SKILL_PARAM_NAME,
|
||||
} from './base-declarations.js';
|
||||
|
||||
// Re-export sets for compatibility
|
||||
|
||||
@@ -17,12 +17,6 @@ import {
|
||||
SHELL_TOOL_NAME,
|
||||
EXIT_PLAN_MODE_TOOL_NAME,
|
||||
ACTIVATE_SKILL_TOOL_NAME,
|
||||
SHELL_PARAM_COMMAND,
|
||||
PARAM_DESCRIPTION,
|
||||
PARAM_DIR_PATH,
|
||||
SHELL_PARAM_IS_BACKGROUND,
|
||||
EXIT_PLAN_PARAM_PLAN_PATH,
|
||||
SKILL_PARAM_NAME,
|
||||
} from './base-declarations.js';
|
||||
|
||||
/**
|
||||
@@ -53,12 +47,12 @@ export function getShellToolDescription(
|
||||
|
||||
if (os.platform() === 'win32') {
|
||||
const backgroundInstructions = enableInteractiveShell
|
||||
? `To run a command in the background, set the \`${SHELL_PARAM_IS_BACKGROUND}\` parameter to true. Do NOT use PowerShell background constructs.`
|
||||
? 'To run a command in the background, set the `is_background` parameter to true. Do NOT use PowerShell background constructs.'
|
||||
: 'Command can start background processes using PowerShell constructs such as `Start-Process -NoNewWindow` or `Start-Job`.';
|
||||
return `This tool executes a given shell command as \`powershell.exe -NoProfile -Command <command>\`. ${backgroundInstructions}${efficiencyGuidelines}${returnedInfo}`;
|
||||
} else {
|
||||
const backgroundInstructions = enableInteractiveShell
|
||||
? `To run a command in the background, set the \`${SHELL_PARAM_IS_BACKGROUND}\` parameter to true. Do NOT use \`&\` to background commands.`
|
||||
? 'To run a command in the background, set the `is_background` parameter to true. Do NOT use `&` to background commands.'
|
||||
: 'Command can start background processes using `&`.';
|
||||
return `This tool executes a given shell command as \`bash -c <command>\`. ${backgroundInstructions} Command is executed as a subprocess that leads its own process group. Command process group can be terminated as \`kill -- -PGID\` or signaled as \`kill -s SIGNAL -- -PGID\`.${efficiencyGuidelines}${returnedInfo}`;
|
||||
}
|
||||
@@ -90,27 +84,27 @@ export function getShellDeclaration(
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[SHELL_PARAM_COMMAND]: {
|
||||
command: {
|
||||
type: 'string',
|
||||
description: getCommandDescription(),
|
||||
},
|
||||
[PARAM_DESCRIPTION]: {
|
||||
description: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Brief description of the command for the user. Be specific and concise. Ideally a single sentence. Can be up to 3 sentences for clarity. No line breaks.',
|
||||
},
|
||||
[PARAM_DIR_PATH]: {
|
||||
dir_path: {
|
||||
type: 'string',
|
||||
description:
|
||||
'(OPTIONAL) The path of the directory to run the command in. If not provided, the project root directory is used. Must be a directory within the workspace and must already exist.',
|
||||
},
|
||||
[SHELL_PARAM_IS_BACKGROUND]: {
|
||||
is_background: {
|
||||
type: 'boolean',
|
||||
description:
|
||||
'Set to true if this command should be run in the background (e.g. for long-running servers or watchers). The command will be started, allowed to run for a brief moment to check for immediate errors, and then moved to the background.',
|
||||
},
|
||||
},
|
||||
required: [SHELL_PARAM_COMMAND],
|
||||
required: ['command'],
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -127,9 +121,9 @@ export function getExitPlanModeDeclaration(
|
||||
'Finalizes the planning phase and transitions to implementation by presenting the plan for user approval. This tool MUST be used to exit Plan Mode before any source code edits can be performed. Call this whenever a plan is ready or the user requests implementation.',
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
required: [EXIT_PLAN_PARAM_PLAN_PATH],
|
||||
required: ['plan_path'],
|
||||
properties: {
|
||||
[EXIT_PLAN_PARAM_PLAN_PATH]: {
|
||||
plan_path: {
|
||||
type: 'string',
|
||||
description: `The file path to the finalized plan (e.g., "${plansDir}/feature-x.md"). This path MUST be within the designated plans directory: ${plansDir}/`,
|
||||
},
|
||||
@@ -152,13 +146,11 @@ export function getActivateSkillDeclaration(
|
||||
let schema: z.ZodTypeAny;
|
||||
if (skillNames.length === 0) {
|
||||
schema = z.object({
|
||||
[SKILL_PARAM_NAME]: z
|
||||
.string()
|
||||
.describe('No skills are currently available.'),
|
||||
name: z.string().describe('No skills are currently available.'),
|
||||
});
|
||||
} else {
|
||||
schema = z.object({
|
||||
[SKILL_PARAM_NAME]: z
|
||||
name: z
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
.enum(skillNames as [string, ...string[]])
|
||||
.describe('The name of the skill to activate.'),
|
||||
|
||||
@@ -25,54 +25,6 @@ import {
|
||||
GET_INTERNAL_DOCS_TOOL_NAME,
|
||||
ASK_USER_TOOL_NAME,
|
||||
ENTER_PLAN_MODE_TOOL_NAME,
|
||||
// Shared parameter names
|
||||
PARAM_FILE_PATH,
|
||||
PARAM_DIR_PATH,
|
||||
PARAM_PATTERN,
|
||||
PARAM_CASE_SENSITIVE,
|
||||
PARAM_RESPECT_GIT_IGNORE,
|
||||
PARAM_RESPECT_GEMINI_IGNORE,
|
||||
PARAM_FILE_FILTERING_OPTIONS,
|
||||
// Tool-specific parameter names
|
||||
READ_FILE_PARAM_START_LINE,
|
||||
READ_FILE_PARAM_END_LINE,
|
||||
WRITE_FILE_PARAM_CONTENT,
|
||||
GREP_PARAM_INCLUDE_PATTERN,
|
||||
GREP_PARAM_EXCLUDE_PATTERN,
|
||||
GREP_PARAM_NAMES_ONLY,
|
||||
GREP_PARAM_MAX_MATCHES_PER_FILE,
|
||||
GREP_PARAM_TOTAL_MAX_MATCHES,
|
||||
GREP_PARAM_FIXED_STRINGS,
|
||||
GREP_PARAM_CONTEXT,
|
||||
GREP_PARAM_AFTER,
|
||||
GREP_PARAM_BEFORE,
|
||||
GREP_PARAM_NO_IGNORE,
|
||||
EDIT_PARAM_INSTRUCTION,
|
||||
EDIT_PARAM_OLD_STRING,
|
||||
EDIT_PARAM_NEW_STRING,
|
||||
EDIT_PARAM_ALLOW_MULTIPLE,
|
||||
LS_PARAM_IGNORE,
|
||||
WEB_SEARCH_PARAM_QUERY,
|
||||
WEB_FETCH_PARAM_PROMPT,
|
||||
READ_MANY_PARAM_INCLUDE,
|
||||
READ_MANY_PARAM_EXCLUDE,
|
||||
READ_MANY_PARAM_RECURSIVE,
|
||||
READ_MANY_PARAM_USE_DEFAULT_EXCLUDES,
|
||||
MEMORY_PARAM_FACT,
|
||||
TODOS_PARAM_TODOS,
|
||||
TODOS_ITEM_PARAM_DESCRIPTION,
|
||||
TODOS_ITEM_PARAM_STATUS,
|
||||
DOCS_PARAM_PATH,
|
||||
ASK_USER_PARAM_QUESTIONS,
|
||||
ASK_USER_QUESTION_PARAM_QUESTION,
|
||||
ASK_USER_QUESTION_PARAM_HEADER,
|
||||
ASK_USER_QUESTION_PARAM_TYPE,
|
||||
ASK_USER_QUESTION_PARAM_OPTIONS,
|
||||
ASK_USER_QUESTION_PARAM_MULTI_SELECT,
|
||||
ASK_USER_QUESTION_PARAM_PLACEHOLDER,
|
||||
ASK_USER_OPTION_PARAM_LABEL,
|
||||
ASK_USER_OPTION_PARAM_DESCRIPTION,
|
||||
PLAN_MODE_PARAM_REASON,
|
||||
} from '../base-declarations.js';
|
||||
import {
|
||||
getShellDeclaration,
|
||||
@@ -87,22 +39,22 @@ export const DEFAULT_LEGACY_SET: CoreToolSet = {
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[PARAM_FILE_PATH]: {
|
||||
file_path: {
|
||||
description: 'The path to the file to read.',
|
||||
type: 'string',
|
||||
},
|
||||
[READ_FILE_PARAM_START_LINE]: {
|
||||
start_line: {
|
||||
description:
|
||||
'Optional: The 1-based line number to start reading from.',
|
||||
type: 'number',
|
||||
},
|
||||
[READ_FILE_PARAM_END_LINE]: {
|
||||
end_line: {
|
||||
description:
|
||||
'Optional: The 1-based line number to end reading at (inclusive).',
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
required: [PARAM_FILE_PATH],
|
||||
required: ['file_path'],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -114,17 +66,17 @@ export const DEFAULT_LEGACY_SET: CoreToolSet = {
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[PARAM_FILE_PATH]: {
|
||||
file_path: {
|
||||
description: 'The path to the file to write to.',
|
||||
type: 'string',
|
||||
},
|
||||
[WRITE_FILE_PARAM_CONTENT]: {
|
||||
content: {
|
||||
description:
|
||||
"The content to write to the file. Do not use omission placeholders like '(rest of methods ...)', '...', or 'unchanged code'; provide complete literal content.",
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: [PARAM_FILE_PATH, WRITE_FILE_PARAM_CONTENT],
|
||||
required: ['file_path', 'content'],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -135,43 +87,43 @@ export const DEFAULT_LEGACY_SET: CoreToolSet = {
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[PARAM_PATTERN]: {
|
||||
pattern: {
|
||||
description: `The regular expression (regex) pattern to search for within file contents (e.g., 'function\\s+myFunction', 'import\\s+\\{.*\\}\\s+from\\s+.*').`,
|
||||
type: 'string',
|
||||
},
|
||||
[PARAM_DIR_PATH]: {
|
||||
dir_path: {
|
||||
description:
|
||||
'Optional: The absolute path to the directory to search within. If omitted, searches the current working directory.',
|
||||
type: 'string',
|
||||
},
|
||||
[GREP_PARAM_INCLUDE_PATTERN]: {
|
||||
include_pattern: {
|
||||
description: `Optional: A glob pattern to filter which files are searched (e.g., '*.js', '*.{ts,tsx}', 'src/**'). If omitted, searches all files (respecting potential global ignores).`,
|
||||
type: 'string',
|
||||
},
|
||||
[GREP_PARAM_EXCLUDE_PATTERN]: {
|
||||
exclude_pattern: {
|
||||
description:
|
||||
'Optional: A regular expression pattern to exclude from the search results. If a line matches both the pattern and the exclude_pattern, it will be omitted.',
|
||||
type: 'string',
|
||||
},
|
||||
[GREP_PARAM_NAMES_ONLY]: {
|
||||
names_only: {
|
||||
description:
|
||||
'Optional: If true, only the file paths of the matches will be returned, without the line content or line numbers. This is useful for gathering a list of files.',
|
||||
type: 'boolean',
|
||||
},
|
||||
[GREP_PARAM_MAX_MATCHES_PER_FILE]: {
|
||||
max_matches_per_file: {
|
||||
description:
|
||||
'Optional: Maximum number of matches to return per file. Use this to prevent being overwhelmed by repetitive matches in large files.',
|
||||
type: 'integer',
|
||||
minimum: 1,
|
||||
},
|
||||
[GREP_PARAM_TOTAL_MAX_MATCHES]: {
|
||||
total_max_matches: {
|
||||
description:
|
||||
'Optional: Maximum number of total matches to return. Use this to limit the overall size of the response. Defaults to 100 if omitted.',
|
||||
type: 'integer',
|
||||
minimum: 1,
|
||||
},
|
||||
},
|
||||
required: [PARAM_PATTERN],
|
||||
required: ['pattern'],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -182,76 +134,76 @@ export const DEFAULT_LEGACY_SET: CoreToolSet = {
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[PARAM_PATTERN]: {
|
||||
pattern: {
|
||||
description: `The pattern to search for. By default, treated as a Rust-flavored regular expression. Use '\\b' for precise symbol matching (e.g., '\\bMatchMe\\b').`,
|
||||
type: 'string',
|
||||
},
|
||||
[PARAM_DIR_PATH]: {
|
||||
dir_path: {
|
||||
description:
|
||||
"Directory or file to search. Directories are searched recursively. Relative paths are resolved against current working directory. Defaults to current working directory ('.') if omitted.",
|
||||
type: 'string',
|
||||
},
|
||||
[GREP_PARAM_INCLUDE_PATTERN]: {
|
||||
include_pattern: {
|
||||
description:
|
||||
"Glob pattern to filter files (e.g., '*.ts', 'src/**'). Recommended for large repositories to reduce noise. Defaults to all files if omitted.",
|
||||
type: 'string',
|
||||
},
|
||||
[GREP_PARAM_EXCLUDE_PATTERN]: {
|
||||
exclude_pattern: {
|
||||
description:
|
||||
'Optional: A regular expression pattern to exclude from the search results. If a line matches both the pattern and the exclude_pattern, it will be omitted.',
|
||||
type: 'string',
|
||||
},
|
||||
[GREP_PARAM_NAMES_ONLY]: {
|
||||
names_only: {
|
||||
description:
|
||||
'Optional: If true, only the file paths of the matches will be returned, without the line content or line numbers. This is useful for gathering a list of files.',
|
||||
type: 'boolean',
|
||||
},
|
||||
[PARAM_CASE_SENSITIVE]: {
|
||||
case_sensitive: {
|
||||
description:
|
||||
'If true, search is case-sensitive. Defaults to false (ignore case) if omitted.',
|
||||
type: 'boolean',
|
||||
},
|
||||
[GREP_PARAM_FIXED_STRINGS]: {
|
||||
fixed_strings: {
|
||||
description:
|
||||
'If true, treats the `pattern` as a literal string instead of a regular expression. Defaults to false (basic regex) if omitted.',
|
||||
type: 'boolean',
|
||||
},
|
||||
[GREP_PARAM_CONTEXT]: {
|
||||
context: {
|
||||
description:
|
||||
'Show this many lines of context around each match (equivalent to grep -C). Defaults to 0 if omitted.',
|
||||
type: 'integer',
|
||||
},
|
||||
[GREP_PARAM_AFTER]: {
|
||||
after: {
|
||||
description:
|
||||
'Show this many lines after each match (equivalent to grep -A). Defaults to 0 if omitted.',
|
||||
type: 'integer',
|
||||
minimum: 0,
|
||||
},
|
||||
[GREP_PARAM_BEFORE]: {
|
||||
before: {
|
||||
description:
|
||||
'Show this many lines before each match (equivalent to grep -B). Defaults to 0 if omitted.',
|
||||
type: 'integer',
|
||||
minimum: 0,
|
||||
},
|
||||
[GREP_PARAM_NO_IGNORE]: {
|
||||
no_ignore: {
|
||||
description:
|
||||
'If true, searches all files including those usually ignored (like in .gitignore, build/, dist/, etc). Defaults to false if omitted.',
|
||||
type: 'boolean',
|
||||
},
|
||||
[GREP_PARAM_MAX_MATCHES_PER_FILE]: {
|
||||
max_matches_per_file: {
|
||||
description:
|
||||
'Optional: Maximum number of matches to return per file. Use this to prevent being overwhelmed by repetitive matches in large files.',
|
||||
type: 'integer',
|
||||
minimum: 1,
|
||||
},
|
||||
[GREP_PARAM_TOTAL_MAX_MATCHES]: {
|
||||
total_max_matches: {
|
||||
description:
|
||||
'Optional: Maximum number of total matches to return. Use this to limit the overall size of the response. Defaults to 100 if omitted.',
|
||||
type: 'integer',
|
||||
minimum: 1,
|
||||
},
|
||||
},
|
||||
required: [PARAM_PATTERN],
|
||||
required: ['pattern'],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -262,33 +214,33 @@ export const DEFAULT_LEGACY_SET: CoreToolSet = {
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[PARAM_PATTERN]: {
|
||||
pattern: {
|
||||
description:
|
||||
"The glob pattern to match against (e.g., '**/*.py', 'docs/*.md').",
|
||||
type: 'string',
|
||||
},
|
||||
[PARAM_DIR_PATH]: {
|
||||
dir_path: {
|
||||
description:
|
||||
'Optional: The absolute path to the directory to search within. If omitted, searches the root directory.',
|
||||
type: 'string',
|
||||
},
|
||||
[PARAM_CASE_SENSITIVE]: {
|
||||
case_sensitive: {
|
||||
description:
|
||||
'Optional: Whether the search should be case-sensitive. Defaults to false.',
|
||||
type: 'boolean',
|
||||
},
|
||||
[PARAM_RESPECT_GIT_IGNORE]: {
|
||||
respect_git_ignore: {
|
||||
description:
|
||||
'Optional: Whether to respect .gitignore patterns when finding files. Only available in git repositories. Defaults to true.',
|
||||
type: 'boolean',
|
||||
},
|
||||
[PARAM_RESPECT_GEMINI_IGNORE]: {
|
||||
respect_gemini_ignore: {
|
||||
description:
|
||||
'Optional: Whether to respect .geminiignore patterns when finding files. Defaults to true.',
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
required: [PARAM_PATTERN],
|
||||
required: ['pattern'],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -299,28 +251,28 @@ export const DEFAULT_LEGACY_SET: CoreToolSet = {
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[PARAM_DIR_PATH]: {
|
||||
dir_path: {
|
||||
description: 'The path to the directory to list',
|
||||
type: 'string',
|
||||
},
|
||||
[LS_PARAM_IGNORE]: {
|
||||
ignore: {
|
||||
description: 'List of glob patterns to ignore',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
[PARAM_FILE_FILTERING_OPTIONS]: {
|
||||
file_filtering_options: {
|
||||
description:
|
||||
'Optional: Whether to respect ignore patterns from .gitignore or .geminiignore',
|
||||
type: 'object',
|
||||
properties: {
|
||||
[PARAM_RESPECT_GIT_IGNORE]: {
|
||||
respect_git_ignore: {
|
||||
description:
|
||||
'Optional: Whether to respect .gitignore patterns when listing files. Only available in git repositories. Defaults to true.',
|
||||
type: 'boolean',
|
||||
},
|
||||
[PARAM_RESPECT_GEMINI_IGNORE]: {
|
||||
respect_gemini_ignore: {
|
||||
description:
|
||||
'Optional: Whether to respect .geminiignore patterns when listing files. Defaults to true.',
|
||||
type: 'boolean',
|
||||
@@ -328,7 +280,7 @@ export const DEFAULT_LEGACY_SET: CoreToolSet = {
|
||||
},
|
||||
},
|
||||
},
|
||||
required: [PARAM_DIR_PATH],
|
||||
required: ['dir_path'],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -352,11 +304,11 @@ export const DEFAULT_LEGACY_SET: CoreToolSet = {
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[PARAM_FILE_PATH]: {
|
||||
file_path: {
|
||||
description: 'The path to the file to modify.',
|
||||
type: 'string',
|
||||
},
|
||||
[EDIT_PARAM_INSTRUCTION]: {
|
||||
instruction: {
|
||||
description: `A clear, semantic instruction for the code change, acting as a high-quality prompt for an expert LLM assistant. It must be self-contained and explain the goal of the change.
|
||||
|
||||
A good instruction should concisely answer:
|
||||
@@ -374,28 +326,23 @@ A good instruction should concisely answer:
|
||||
`,
|
||||
type: 'string',
|
||||
},
|
||||
[EDIT_PARAM_OLD_STRING]: {
|
||||
old_string: {
|
||||
description:
|
||||
'The exact literal text to replace, preferably unescaped. For single replacements (default), include at least 3 lines of context BEFORE and AFTER the target text, matching whitespace and indentation precisely. If this string is not the exact literal text (i.e. you escaped it) or does not match exactly, the tool will fail.',
|
||||
type: 'string',
|
||||
},
|
||||
[EDIT_PARAM_NEW_STRING]: {
|
||||
new_string: {
|
||||
description:
|
||||
"The exact literal text to replace `old_string` with, preferably unescaped. Provide the EXACT text. Ensure the resulting code is correct and idiomatic. Do not use omission placeholders like '(rest of methods ...)', '...', or 'unchanged code'; provide exact literal code.",
|
||||
type: 'string',
|
||||
},
|
||||
[EDIT_PARAM_ALLOW_MULTIPLE]: {
|
||||
allow_multiple: {
|
||||
type: 'boolean',
|
||||
description:
|
||||
'If true, the tool will replace all occurrences of `old_string`. If false (default), it will only succeed if exactly one occurrence is found.',
|
||||
},
|
||||
},
|
||||
required: [
|
||||
PARAM_FILE_PATH,
|
||||
EDIT_PARAM_INSTRUCTION,
|
||||
EDIT_PARAM_OLD_STRING,
|
||||
EDIT_PARAM_NEW_STRING,
|
||||
],
|
||||
required: ['file_path', 'instruction', 'old_string', 'new_string'],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -406,12 +353,12 @@ A good instruction should concisely answer:
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[WEB_SEARCH_PARAM_QUERY]: {
|
||||
query: {
|
||||
type: 'string',
|
||||
description: 'The search query to find information on the web.',
|
||||
},
|
||||
},
|
||||
required: [WEB_SEARCH_PARAM_QUERY],
|
||||
required: ['query'],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -422,13 +369,13 @@ A good instruction should concisely answer:
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[WEB_FETCH_PARAM_PROMPT]: {
|
||||
prompt: {
|
||||
description:
|
||||
'A comprehensive prompt that includes the URL(s) (up to 20) to fetch and specific instructions on how to process their content (e.g., "Summarize https://example.com/article and extract key points from https://another.com/data"). All URLs to be fetched must be valid and complete, starting with "http://" or "https://", and be fully-formed with a valid hostname (e.g., a domain name like "example.com" or an IP address). For example, "https://example.com" is valid, but "example.com" is not.',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: [WEB_FETCH_PARAM_PROMPT],
|
||||
required: ['prompt'],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -447,7 +394,7 @@ Use this tool when the user's query implies needing the content of several files
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[READ_MANY_PARAM_INCLUDE]: {
|
||||
include: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
@@ -457,7 +404,7 @@ Use this tool when the user's query implies needing the content of several files
|
||||
description:
|
||||
'An array of glob patterns or paths. Examples: ["src/**/*.ts"], ["README.md", "docs/"]',
|
||||
},
|
||||
[READ_MANY_PARAM_EXCLUDE]: {
|
||||
exclude: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
@@ -467,30 +414,30 @@ Use this tool when the user's query implies needing the content of several files
|
||||
'Optional. Glob patterns for files/directories to exclude. Added to default excludes if useDefaultExcludes is true. Example: "**/*.log", "temp/"',
|
||||
default: [],
|
||||
},
|
||||
[READ_MANY_PARAM_RECURSIVE]: {
|
||||
recursive: {
|
||||
type: 'boolean',
|
||||
description:
|
||||
'Optional. Whether to search recursively (primarily controlled by `**` in glob patterns). Defaults to true.',
|
||||
default: true,
|
||||
},
|
||||
|
||||
[READ_MANY_PARAM_USE_DEFAULT_EXCLUDES]: {
|
||||
useDefaultExcludes: {
|
||||
type: 'boolean',
|
||||
description:
|
||||
'Optional. Whether to apply a list of default exclusion patterns (e.g., node_modules, .git, binary files). Defaults to true.',
|
||||
default: true,
|
||||
},
|
||||
[PARAM_FILE_FILTERING_OPTIONS]: {
|
||||
file_filtering_options: {
|
||||
description:
|
||||
'Whether to respect ignore patterns from .gitignore or .geminiignore',
|
||||
type: 'object',
|
||||
properties: {
|
||||
[PARAM_RESPECT_GIT_IGNORE]: {
|
||||
respect_git_ignore: {
|
||||
description:
|
||||
'Optional: Whether to respect .gitignore patterns when listing files. Only available in git repositories. Defaults to true.',
|
||||
type: 'boolean',
|
||||
},
|
||||
[PARAM_RESPECT_GEMINI_IGNORE]: {
|
||||
respect_gemini_ignore: {
|
||||
description:
|
||||
'Optional: Whether to respect .geminiignore patterns when listing files. Defaults to true.',
|
||||
type: 'boolean',
|
||||
@@ -498,7 +445,7 @@ Use this tool when the user's query implies needing the content of several files
|
||||
},
|
||||
},
|
||||
},
|
||||
required: [READ_MANY_PARAM_INCLUDE],
|
||||
required: ['include'],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -515,13 +462,13 @@ NEVER save workspace-specific context, local paths, or commands (e.g. "The entry
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[MEMORY_PARAM_FACT]: {
|
||||
fact: {
|
||||
type: 'string',
|
||||
description:
|
||||
'The specific fact or piece of information to remember. Should be a clear, self-contained statement.',
|
||||
},
|
||||
},
|
||||
required: [MEMORY_PARAM_FACT],
|
||||
required: ['fact'],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
@@ -594,7 +541,7 @@ The agent did not use the todo list because this task could be completed by a ti
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[TODOS_PARAM_TODOS]: {
|
||||
todos: {
|
||||
type: 'array',
|
||||
description:
|
||||
'The complete list of todo items. This will replace the existing list.',
|
||||
@@ -602,22 +549,22 @@ The agent did not use the todo list because this task could be completed by a ti
|
||||
type: 'object',
|
||||
description: 'A single todo item.',
|
||||
properties: {
|
||||
[TODOS_ITEM_PARAM_DESCRIPTION]: {
|
||||
description: {
|
||||
type: 'string',
|
||||
description: 'The description of the task.',
|
||||
},
|
||||
[TODOS_ITEM_PARAM_STATUS]: {
|
||||
status: {
|
||||
type: 'string',
|
||||
description: 'The current status of the task.',
|
||||
enum: ['pending', 'in_progress', 'completed', 'cancelled'],
|
||||
},
|
||||
},
|
||||
required: [TODOS_ITEM_PARAM_DESCRIPTION, TODOS_ITEM_PARAM_STATUS],
|
||||
required: ['description', 'status'],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
required: [TODOS_PARAM_TODOS],
|
||||
required: ['todos'],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
@@ -629,7 +576,7 @@ The agent did not use the todo list because this task could be completed by a ti
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[DOCS_PARAM_PATH]: {
|
||||
path: {
|
||||
description:
|
||||
"The relative path to the documentation file (e.g., 'cli/commands.md'). If omitted, lists all available documentation.",
|
||||
type: 'string',
|
||||
@@ -644,54 +591,47 @@ The agent did not use the todo list because this task could be completed by a ti
|
||||
'Ask the user one or more questions to gather preferences, clarify requirements, or make decisions.',
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
required: [ASK_USER_PARAM_QUESTIONS],
|
||||
required: ['questions'],
|
||||
properties: {
|
||||
[ASK_USER_PARAM_QUESTIONS]: {
|
||||
questions: {
|
||||
type: 'array',
|
||||
minItems: 1,
|
||||
maxItems: 4,
|
||||
items: {
|
||||
type: 'object',
|
||||
required: [
|
||||
ASK_USER_QUESTION_PARAM_QUESTION,
|
||||
ASK_USER_QUESTION_PARAM_HEADER,
|
||||
ASK_USER_QUESTION_PARAM_TYPE,
|
||||
],
|
||||
required: ['question', 'header', 'type'],
|
||||
properties: {
|
||||
[ASK_USER_QUESTION_PARAM_QUESTION]: {
|
||||
question: {
|
||||
type: 'string',
|
||||
description:
|
||||
'The complete question to ask the user. Should be clear, specific, and end with a question mark.',
|
||||
},
|
||||
[ASK_USER_QUESTION_PARAM_HEADER]: {
|
||||
header: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Very short label displayed as a chip/tag. Use abbreviations: "Auth" not "Authentication", "Config" not "Configuration". Examples: "Auth method", "Library", "Approach", "Database".',
|
||||
},
|
||||
[ASK_USER_QUESTION_PARAM_TYPE]: {
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['choice', 'text', 'yesno'],
|
||||
default: 'choice',
|
||||
description:
|
||||
"Question type: 'choice' (default) for multiple-choice with options, 'text' for free-form input, 'yesno' for Yes/No confirmation.",
|
||||
},
|
||||
[ASK_USER_QUESTION_PARAM_OPTIONS]: {
|
||||
options: {
|
||||
type: 'array',
|
||||
description:
|
||||
"The selectable choices for 'choice' type questions. Provide 2-4 options. An 'Other' option is automatically added. Not needed for 'text' or 'yesno' types.",
|
||||
items: {
|
||||
type: 'object',
|
||||
required: [
|
||||
ASK_USER_OPTION_PARAM_LABEL,
|
||||
ASK_USER_OPTION_PARAM_DESCRIPTION,
|
||||
],
|
||||
required: ['label', 'description'],
|
||||
properties: {
|
||||
[ASK_USER_OPTION_PARAM_LABEL]: {
|
||||
label: {
|
||||
type: 'string',
|
||||
description:
|
||||
'The display text for this option (1-5 words). Example: "OAuth 2.0"',
|
||||
},
|
||||
[ASK_USER_OPTION_PARAM_DESCRIPTION]: {
|
||||
description: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Brief explanation of this option. Example: "Industry standard, supports SSO"',
|
||||
@@ -699,12 +639,12 @@ The agent did not use the todo list because this task could be completed by a ti
|
||||
},
|
||||
},
|
||||
},
|
||||
[ASK_USER_QUESTION_PARAM_MULTI_SELECT]: {
|
||||
multiSelect: {
|
||||
type: 'boolean',
|
||||
description:
|
||||
"Only applies when type='choice'. Set to true to allow selecting multiple options.",
|
||||
},
|
||||
[ASK_USER_QUESTION_PARAM_PLACEHOLDER]: {
|
||||
placeholder: {
|
||||
type: 'string',
|
||||
description:
|
||||
"Hint text shown in the input field. For type='text', shown in the main input. For type='choice', shown in the 'Other' custom input.",
|
||||
@@ -723,7 +663,7 @@ The agent did not use the todo list because this task could be completed by a ti
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[PLAN_MODE_PARAM_REASON]: {
|
||||
reason: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Short reason explaining why you are entering plan mode.',
|
||||
|
||||
@@ -25,54 +25,6 @@ import {
|
||||
GET_INTERNAL_DOCS_TOOL_NAME,
|
||||
ASK_USER_TOOL_NAME,
|
||||
ENTER_PLAN_MODE_TOOL_NAME,
|
||||
// Shared parameter names
|
||||
PARAM_FILE_PATH,
|
||||
PARAM_DIR_PATH,
|
||||
PARAM_PATTERN,
|
||||
PARAM_CASE_SENSITIVE,
|
||||
PARAM_RESPECT_GIT_IGNORE,
|
||||
PARAM_RESPECT_GEMINI_IGNORE,
|
||||
PARAM_FILE_FILTERING_OPTIONS,
|
||||
// Tool-specific parameter names
|
||||
READ_FILE_PARAM_START_LINE,
|
||||
READ_FILE_PARAM_END_LINE,
|
||||
WRITE_FILE_PARAM_CONTENT,
|
||||
GREP_PARAM_INCLUDE_PATTERN,
|
||||
GREP_PARAM_EXCLUDE_PATTERN,
|
||||
GREP_PARAM_NAMES_ONLY,
|
||||
GREP_PARAM_MAX_MATCHES_PER_FILE,
|
||||
GREP_PARAM_TOTAL_MAX_MATCHES,
|
||||
GREP_PARAM_FIXED_STRINGS,
|
||||
GREP_PARAM_CONTEXT,
|
||||
GREP_PARAM_AFTER,
|
||||
GREP_PARAM_BEFORE,
|
||||
GREP_PARAM_NO_IGNORE,
|
||||
EDIT_PARAM_INSTRUCTION,
|
||||
EDIT_PARAM_OLD_STRING,
|
||||
EDIT_PARAM_NEW_STRING,
|
||||
EDIT_PARAM_ALLOW_MULTIPLE,
|
||||
LS_PARAM_IGNORE,
|
||||
WEB_SEARCH_PARAM_QUERY,
|
||||
WEB_FETCH_PARAM_PROMPT,
|
||||
READ_MANY_PARAM_INCLUDE,
|
||||
READ_MANY_PARAM_EXCLUDE,
|
||||
READ_MANY_PARAM_RECURSIVE,
|
||||
READ_MANY_PARAM_USE_DEFAULT_EXCLUDES,
|
||||
MEMORY_PARAM_FACT,
|
||||
TODOS_PARAM_TODOS,
|
||||
TODOS_ITEM_PARAM_DESCRIPTION,
|
||||
TODOS_ITEM_PARAM_STATUS,
|
||||
DOCS_PARAM_PATH,
|
||||
ASK_USER_PARAM_QUESTIONS,
|
||||
ASK_USER_QUESTION_PARAM_QUESTION,
|
||||
ASK_USER_QUESTION_PARAM_HEADER,
|
||||
ASK_USER_QUESTION_PARAM_TYPE,
|
||||
ASK_USER_QUESTION_PARAM_OPTIONS,
|
||||
ASK_USER_QUESTION_PARAM_MULTI_SELECT,
|
||||
ASK_USER_QUESTION_PARAM_PLACEHOLDER,
|
||||
ASK_USER_OPTION_PARAM_LABEL,
|
||||
ASK_USER_OPTION_PARAM_DESCRIPTION,
|
||||
PLAN_MODE_PARAM_REASON,
|
||||
} from '../base-declarations.js';
|
||||
import {
|
||||
getShellDeclaration,
|
||||
@@ -90,22 +42,22 @@ export const GEMINI_3_SET: CoreToolSet = {
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[PARAM_FILE_PATH]: {
|
||||
file_path: {
|
||||
description: 'The path to the file to read.',
|
||||
type: 'string',
|
||||
},
|
||||
[READ_FILE_PARAM_START_LINE]: {
|
||||
start_line: {
|
||||
description:
|
||||
'Optional: The 1-based line number to start reading from.',
|
||||
type: 'number',
|
||||
},
|
||||
[READ_FILE_PARAM_END_LINE]: {
|
||||
end_line: {
|
||||
description:
|
||||
'Optional: The 1-based line number to end reading at (inclusive).',
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
required: [PARAM_FILE_PATH],
|
||||
required: ['file_path'],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -115,17 +67,17 @@ export const GEMINI_3_SET: CoreToolSet = {
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[PARAM_FILE_PATH]: {
|
||||
file_path: {
|
||||
description: 'Path to the file.',
|
||||
type: 'string',
|
||||
},
|
||||
[WRITE_FILE_PARAM_CONTENT]: {
|
||||
content: {
|
||||
description:
|
||||
"The complete content to write. Provide the full file; do not use placeholders like '// ... rest of code'.",
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: [PARAM_FILE_PATH, WRITE_FILE_PARAM_CONTENT],
|
||||
required: ['file_path', 'content'],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -136,43 +88,43 @@ export const GEMINI_3_SET: CoreToolSet = {
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[PARAM_PATTERN]: {
|
||||
pattern: {
|
||||
description: `The regular expression (regex) pattern to search for within file contents (e.g., 'function\\s+myFunction', 'import\\s+\\{.*\\}\\s+from\\s+.*').`,
|
||||
type: 'string',
|
||||
},
|
||||
[PARAM_DIR_PATH]: {
|
||||
dir_path: {
|
||||
description:
|
||||
'Optional: The absolute path to the directory to search within. If omitted, searches the current working directory.',
|
||||
type: 'string',
|
||||
},
|
||||
[GREP_PARAM_INCLUDE_PATTERN]: {
|
||||
include_pattern: {
|
||||
description: `Optional: A glob pattern to filter which files are searched (e.g., '*.js', '*.{ts,tsx}', 'src/**'). If omitted, searches all files (respecting potential global ignores).`,
|
||||
type: 'string',
|
||||
},
|
||||
[GREP_PARAM_EXCLUDE_PATTERN]: {
|
||||
exclude_pattern: {
|
||||
description:
|
||||
'Optional: A regular expression pattern to exclude from the search results. If a line matches both the pattern and the exclude_pattern, it will be omitted.',
|
||||
type: 'string',
|
||||
},
|
||||
[GREP_PARAM_NAMES_ONLY]: {
|
||||
names_only: {
|
||||
description:
|
||||
'Optional: If true, only the file paths of the matches will be returned, without the line content or line numbers. This is useful for gathering a list of files.',
|
||||
type: 'boolean',
|
||||
},
|
||||
[GREP_PARAM_MAX_MATCHES_PER_FILE]: {
|
||||
max_matches_per_file: {
|
||||
description:
|
||||
'Optional: Maximum number of matches to return per file. Use this to prevent being overwhelmed by repetitive matches in large files.',
|
||||
type: 'integer',
|
||||
minimum: 1,
|
||||
},
|
||||
[GREP_PARAM_TOTAL_MAX_MATCHES]: {
|
||||
total_max_matches: {
|
||||
description:
|
||||
'Optional: Maximum number of total matches to return. Use this to limit the overall size of the response. Defaults to 100 if omitted.',
|
||||
type: 'integer',
|
||||
minimum: 1,
|
||||
},
|
||||
},
|
||||
required: [PARAM_PATTERN],
|
||||
required: ['pattern'],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -183,76 +135,76 @@ export const GEMINI_3_SET: CoreToolSet = {
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[PARAM_PATTERN]: {
|
||||
pattern: {
|
||||
description: `The pattern to search for. By default, treated as a Rust-flavored regular expression. Use '\\b' for precise symbol matching (e.g., '\\bMatchMe\\b').`,
|
||||
type: 'string',
|
||||
},
|
||||
[PARAM_DIR_PATH]: {
|
||||
dir_path: {
|
||||
description:
|
||||
"Directory or file to search. Directories are searched recursively. Relative paths are resolved against current working directory. Defaults to current working directory ('.') if omitted.",
|
||||
type: 'string',
|
||||
},
|
||||
[GREP_PARAM_INCLUDE_PATTERN]: {
|
||||
include_pattern: {
|
||||
description:
|
||||
"Glob pattern to filter files (e.g., '*.ts', 'src/**'). Recommended for large repositories to reduce noise. Defaults to all files if omitted.",
|
||||
type: 'string',
|
||||
},
|
||||
[GREP_PARAM_EXCLUDE_PATTERN]: {
|
||||
exclude_pattern: {
|
||||
description:
|
||||
'Optional: A regular expression pattern to exclude from the search results. If a line matches both the pattern and the exclude_pattern, it will be omitted.',
|
||||
type: 'string',
|
||||
},
|
||||
[GREP_PARAM_NAMES_ONLY]: {
|
||||
names_only: {
|
||||
description:
|
||||
'Optional: If true, only the file paths of the matches will be returned, without the line content or line numbers. This is useful for gathering a list of files.',
|
||||
type: 'boolean',
|
||||
},
|
||||
[PARAM_CASE_SENSITIVE]: {
|
||||
case_sensitive: {
|
||||
description:
|
||||
'If true, search is case-sensitive. Defaults to false (ignore case) if omitted.',
|
||||
type: 'boolean',
|
||||
},
|
||||
[GREP_PARAM_FIXED_STRINGS]: {
|
||||
fixed_strings: {
|
||||
description:
|
||||
'If true, treats the `pattern` as a literal string instead of a regular expression. Defaults to false (basic regex) if omitted.',
|
||||
type: 'boolean',
|
||||
},
|
||||
[GREP_PARAM_CONTEXT]: {
|
||||
context: {
|
||||
description:
|
||||
'Show this many lines of context around each match (equivalent to grep -C). Defaults to 0 if omitted.',
|
||||
type: 'integer',
|
||||
},
|
||||
[GREP_PARAM_AFTER]: {
|
||||
after: {
|
||||
description:
|
||||
'Show this many lines after each match (equivalent to grep -A). Defaults to 0 if omitted.',
|
||||
type: 'integer',
|
||||
minimum: 0,
|
||||
},
|
||||
[GREP_PARAM_BEFORE]: {
|
||||
before: {
|
||||
description:
|
||||
'Show this many lines before each match (equivalent to grep -B). Defaults to 0 if omitted.',
|
||||
type: 'integer',
|
||||
minimum: 0,
|
||||
},
|
||||
[GREP_PARAM_NO_IGNORE]: {
|
||||
no_ignore: {
|
||||
description:
|
||||
'If true, searches all files including those usually ignored (like in .gitignore, build/, dist/, etc). Defaults to false if omitted.',
|
||||
type: 'boolean',
|
||||
},
|
||||
[GREP_PARAM_MAX_MATCHES_PER_FILE]: {
|
||||
max_matches_per_file: {
|
||||
description:
|
||||
'Optional: Maximum number of matches to return per file. Use this to prevent being overwhelmed by repetitive matches in large files.',
|
||||
type: 'integer',
|
||||
minimum: 1,
|
||||
},
|
||||
[GREP_PARAM_TOTAL_MAX_MATCHES]: {
|
||||
total_max_matches: {
|
||||
description:
|
||||
'Optional: Maximum number of total matches to return. Use this to limit the overall size of the response. Defaults to 100 if omitted.',
|
||||
type: 'integer',
|
||||
minimum: 1,
|
||||
},
|
||||
},
|
||||
required: [PARAM_PATTERN],
|
||||
required: ['pattern'],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -263,33 +215,33 @@ export const GEMINI_3_SET: CoreToolSet = {
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[PARAM_PATTERN]: {
|
||||
pattern: {
|
||||
description:
|
||||
"The glob pattern to match against (e.g., '**/*.py', 'docs/*.md').",
|
||||
type: 'string',
|
||||
},
|
||||
[PARAM_DIR_PATH]: {
|
||||
dir_path: {
|
||||
description:
|
||||
'Optional: The absolute path to the directory to search within. If omitted, searches the root directory.',
|
||||
type: 'string',
|
||||
},
|
||||
[PARAM_CASE_SENSITIVE]: {
|
||||
case_sensitive: {
|
||||
description:
|
||||
'Optional: Whether the search should be case-sensitive. Defaults to false.',
|
||||
type: 'boolean',
|
||||
},
|
||||
[PARAM_RESPECT_GIT_IGNORE]: {
|
||||
respect_git_ignore: {
|
||||
description:
|
||||
'Optional: Whether to respect .gitignore patterns when finding files. Only available in git repositories. Defaults to true.',
|
||||
type: 'boolean',
|
||||
},
|
||||
[PARAM_RESPECT_GEMINI_IGNORE]: {
|
||||
respect_gemini_ignore: {
|
||||
description:
|
||||
'Optional: Whether to respect .geminiignore patterns when finding files. Defaults to true.',
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
required: [PARAM_PATTERN],
|
||||
required: ['pattern'],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -300,28 +252,28 @@ export const GEMINI_3_SET: CoreToolSet = {
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[PARAM_DIR_PATH]: {
|
||||
dir_path: {
|
||||
description: 'The path to the directory to list',
|
||||
type: 'string',
|
||||
},
|
||||
[LS_PARAM_IGNORE]: {
|
||||
ignore: {
|
||||
description: 'List of glob patterns to ignore',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
[PARAM_FILE_FILTERING_OPTIONS]: {
|
||||
file_filtering_options: {
|
||||
description:
|
||||
'Optional: Whether to respect ignore patterns from .gitignore or .geminiignore',
|
||||
type: 'object',
|
||||
properties: {
|
||||
[PARAM_RESPECT_GIT_IGNORE]: {
|
||||
respect_git_ignore: {
|
||||
description:
|
||||
'Optional: Whether to respect .gitignore patterns when listing files. Only available in git repositories. Defaults to true.',
|
||||
type: 'boolean',
|
||||
},
|
||||
[PARAM_RESPECT_GEMINI_IGNORE]: {
|
||||
respect_gemini_ignore: {
|
||||
description:
|
||||
'Optional: Whether to respect .geminiignore patterns when listing files. Defaults to true.',
|
||||
type: 'boolean',
|
||||
@@ -329,7 +281,7 @@ export const GEMINI_3_SET: CoreToolSet = {
|
||||
},
|
||||
},
|
||||
},
|
||||
required: [PARAM_DIR_PATH],
|
||||
required: ['dir_path'],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -343,36 +295,31 @@ The user has the ability to modify the \`new_string\` content. If modified, this
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[PARAM_FILE_PATH]: {
|
||||
file_path: {
|
||||
description: 'The path to the file to modify.',
|
||||
type: 'string',
|
||||
},
|
||||
[EDIT_PARAM_INSTRUCTION]: {
|
||||
instruction: {
|
||||
description: `A clear, semantic instruction for the code change, acting as a high-quality prompt for an expert LLM assistant. It must be self-contained and explain the goal of the change.`,
|
||||
type: 'string',
|
||||
},
|
||||
[EDIT_PARAM_OLD_STRING]: {
|
||||
old_string: {
|
||||
description:
|
||||
'The exact literal text to replace, unescaped. If this string is not the exact literal text (i.e. you escaped it) or does not match exactly, the tool will fail.',
|
||||
type: 'string',
|
||||
},
|
||||
[EDIT_PARAM_NEW_STRING]: {
|
||||
new_string: {
|
||||
description:
|
||||
"The exact literal text to replace `old_string` with, unescaped. Provide the EXACT text. Ensure the resulting code is correct and idiomatic. Do not use omission placeholders like '(rest of methods ...)', '...', or 'unchanged code'; provide exact literal code.",
|
||||
type: 'string',
|
||||
},
|
||||
[EDIT_PARAM_ALLOW_MULTIPLE]: {
|
||||
allow_multiple: {
|
||||
type: 'boolean',
|
||||
description:
|
||||
'If true, the tool will replace all occurrences of `old_string`. If false (default), it will only succeed if exactly one occurrence is found.',
|
||||
},
|
||||
},
|
||||
required: [
|
||||
PARAM_FILE_PATH,
|
||||
EDIT_PARAM_INSTRUCTION,
|
||||
EDIT_PARAM_OLD_STRING,
|
||||
EDIT_PARAM_NEW_STRING,
|
||||
],
|
||||
required: ['file_path', 'instruction', 'old_string', 'new_string'],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -382,13 +329,13 @@ The user has the ability to modify the \`new_string\` content. If modified, this
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[WEB_SEARCH_PARAM_QUERY]: {
|
||||
query: {
|
||||
type: 'string',
|
||||
description:
|
||||
"The search query. Supports natural language questions (e.g., 'Latest breaking changes in React 19') or specific technical queries.",
|
||||
},
|
||||
},
|
||||
required: [WEB_SEARCH_PARAM_QUERY],
|
||||
required: ['query'],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -399,13 +346,13 @@ The user has the ability to modify the \`new_string\` content. If modified, this
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[WEB_FETCH_PARAM_PROMPT]: {
|
||||
prompt: {
|
||||
description:
|
||||
'A string containing the URL(s) and your specific analysis instructions. Be clear about what information you want to find or summarize. Supports up to 20 URLs.',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: [WEB_FETCH_PARAM_PROMPT],
|
||||
required: ['prompt'],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -424,7 +371,7 @@ Use this tool when the user's query implies needing the content of several files
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[READ_MANY_PARAM_INCLUDE]: {
|
||||
include: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
@@ -434,7 +381,7 @@ Use this tool when the user's query implies needing the content of several files
|
||||
description:
|
||||
'An array of glob patterns or paths. Examples: ["src/**/*.ts"], ["README.md", "docs/"]',
|
||||
},
|
||||
[READ_MANY_PARAM_EXCLUDE]: {
|
||||
exclude: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
@@ -444,30 +391,30 @@ Use this tool when the user's query implies needing the content of several files
|
||||
'Optional. Glob patterns for files/directories to exclude. Added to default excludes if useDefaultExcludes is true. Example: "**/*.log", "temp/"',
|
||||
default: [],
|
||||
},
|
||||
[READ_MANY_PARAM_RECURSIVE]: {
|
||||
recursive: {
|
||||
type: 'boolean',
|
||||
description:
|
||||
'Optional. Whether to search recursively (primarily controlled by `**` in glob patterns). Defaults to true.',
|
||||
default: true,
|
||||
},
|
||||
|
||||
[READ_MANY_PARAM_USE_DEFAULT_EXCLUDES]: {
|
||||
useDefaultExcludes: {
|
||||
type: 'boolean',
|
||||
description:
|
||||
'Optional. Whether to apply a list of default exclusion patterns (e.g., node_modules, .git, binary files). Defaults to true.',
|
||||
default: true,
|
||||
},
|
||||
[PARAM_FILE_FILTERING_OPTIONS]: {
|
||||
file_filtering_options: {
|
||||
description:
|
||||
'Whether to respect ignore patterns from .gitignore or .geminiignore',
|
||||
type: 'object',
|
||||
properties: {
|
||||
[PARAM_RESPECT_GIT_IGNORE]: {
|
||||
respect_git_ignore: {
|
||||
description:
|
||||
'Optional: Whether to respect .gitignore patterns when listing files. Only available in git repositories. Defaults to true.',
|
||||
type: 'boolean',
|
||||
},
|
||||
[PARAM_RESPECT_GEMINI_IGNORE]: {
|
||||
respect_gemini_ignore: {
|
||||
description:
|
||||
'Optional: Whether to respect .geminiignore patterns when listing files. Defaults to true.',
|
||||
type: 'boolean',
|
||||
@@ -475,7 +422,7 @@ Use this tool when the user's query implies needing the content of several files
|
||||
},
|
||||
},
|
||||
},
|
||||
required: [READ_MANY_PARAM_INCLUDE],
|
||||
required: ['include'],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -485,13 +432,13 @@ Use this tool when the user's query implies needing the content of several files
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[MEMORY_PARAM_FACT]: {
|
||||
fact: {
|
||||
type: 'string',
|
||||
description:
|
||||
"A concise, global fact or preference (e.g., 'I prefer using tabs'). Do not include local paths or project-specific names.",
|
||||
},
|
||||
},
|
||||
required: [MEMORY_PARAM_FACT],
|
||||
required: ['fact'],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
@@ -564,7 +511,7 @@ The agent did not use the todo list because this task could be completed by a ti
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[TODOS_PARAM_TODOS]: {
|
||||
todos: {
|
||||
type: 'array',
|
||||
description:
|
||||
'The complete list of todo items. This will replace the existing list.',
|
||||
@@ -572,22 +519,22 @@ The agent did not use the todo list because this task could be completed by a ti
|
||||
type: 'object',
|
||||
description: 'A single todo item.',
|
||||
properties: {
|
||||
[TODOS_ITEM_PARAM_DESCRIPTION]: {
|
||||
description: {
|
||||
type: 'string',
|
||||
description: 'The description of the task.',
|
||||
},
|
||||
[TODOS_ITEM_PARAM_STATUS]: {
|
||||
status: {
|
||||
type: 'string',
|
||||
description: 'The current status of the task.',
|
||||
enum: ['pending', 'in_progress', 'completed', 'cancelled'],
|
||||
},
|
||||
},
|
||||
required: [TODOS_ITEM_PARAM_DESCRIPTION, TODOS_ITEM_PARAM_STATUS],
|
||||
required: ['description', 'status'],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
required: [TODOS_PARAM_TODOS],
|
||||
required: ['todos'],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
@@ -599,7 +546,7 @@ The agent did not use the todo list because this task could be completed by a ti
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[DOCS_PARAM_PATH]: {
|
||||
path: {
|
||||
description:
|
||||
"The relative path to the documentation file (e.g., 'cli/commands.md'). If omitted, lists all available documentation.",
|
||||
type: 'string',
|
||||
@@ -614,54 +561,47 @@ The agent did not use the todo list because this task could be completed by a ti
|
||||
'Ask the user one or more questions to gather preferences, clarify requirements, or make decisions. When using this tool, prefer providing multiple-choice options with detailed descriptions and enable multi-select where appropriate to provide maximum flexibility.',
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
required: [ASK_USER_PARAM_QUESTIONS],
|
||||
required: ['questions'],
|
||||
properties: {
|
||||
[ASK_USER_PARAM_QUESTIONS]: {
|
||||
questions: {
|
||||
type: 'array',
|
||||
minItems: 1,
|
||||
maxItems: 4,
|
||||
items: {
|
||||
type: 'object',
|
||||
required: [
|
||||
ASK_USER_QUESTION_PARAM_QUESTION,
|
||||
ASK_USER_QUESTION_PARAM_HEADER,
|
||||
ASK_USER_QUESTION_PARAM_TYPE,
|
||||
],
|
||||
required: ['question', 'header', 'type'],
|
||||
properties: {
|
||||
[ASK_USER_QUESTION_PARAM_QUESTION]: {
|
||||
question: {
|
||||
type: 'string',
|
||||
description:
|
||||
'The complete question to ask the user. Should be clear, specific, and end with a question mark.',
|
||||
},
|
||||
[ASK_USER_QUESTION_PARAM_HEADER]: {
|
||||
header: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Very short label displayed as a chip/tag. Use abbreviations: "Auth" not "Authentication", "Config" not "Configuration". Examples: "Auth method", "Library", "Approach", "Database".',
|
||||
},
|
||||
[ASK_USER_QUESTION_PARAM_TYPE]: {
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['choice', 'text', 'yesno'],
|
||||
default: 'choice',
|
||||
description:
|
||||
"Question type: 'choice' (default) for multiple-choice with options, 'text' for free-form input, 'yesno' for Yes/No confirmation.",
|
||||
},
|
||||
[ASK_USER_QUESTION_PARAM_OPTIONS]: {
|
||||
options: {
|
||||
type: 'array',
|
||||
description:
|
||||
"The selectable choices for 'choice' type questions. Provide 2-4 options. An 'Other' option is automatically added. Not needed for 'text' or 'yesno' types.",
|
||||
items: {
|
||||
type: 'object',
|
||||
required: [
|
||||
ASK_USER_OPTION_PARAM_LABEL,
|
||||
ASK_USER_OPTION_PARAM_DESCRIPTION,
|
||||
],
|
||||
required: ['label', 'description'],
|
||||
properties: {
|
||||
[ASK_USER_OPTION_PARAM_LABEL]: {
|
||||
label: {
|
||||
type: 'string',
|
||||
description:
|
||||
'The display text for this option (1-5 words). Example: "OAuth 2.0"',
|
||||
},
|
||||
[ASK_USER_OPTION_PARAM_DESCRIPTION]: {
|
||||
description: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Brief explanation of this option. Example: "Industry standard, supports SSO"',
|
||||
@@ -669,12 +609,12 @@ The agent did not use the todo list because this task could be completed by a ti
|
||||
},
|
||||
},
|
||||
},
|
||||
[ASK_USER_QUESTION_PARAM_MULTI_SELECT]: {
|
||||
multiSelect: {
|
||||
type: 'boolean',
|
||||
description:
|
||||
"Only applies when type='choice'. Set to true to allow selecting multiple options.",
|
||||
},
|
||||
[ASK_USER_QUESTION_PARAM_PLACEHOLDER]: {
|
||||
placeholder: {
|
||||
type: 'string',
|
||||
description:
|
||||
"Hint text shown in the input field. For type='text', shown in the main input. For type='choice', shown in the 'Other' custom input.",
|
||||
@@ -693,7 +633,7 @@ The agent did not use the todo list because this task could be completed by a ti
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[PLAN_MODE_PARAM_REASON]: {
|
||||
reason: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Short reason explaining why you are entering plan mode.',
|
||||
|
||||
@@ -22,59 +22,6 @@ import {
|
||||
ASK_USER_TOOL_NAME,
|
||||
EXIT_PLAN_MODE_TOOL_NAME,
|
||||
ENTER_PLAN_MODE_TOOL_NAME,
|
||||
// Shared parameter names
|
||||
PARAM_FILE_PATH,
|
||||
PARAM_DIR_PATH,
|
||||
PARAM_PATTERN,
|
||||
PARAM_CASE_SENSITIVE,
|
||||
PARAM_RESPECT_GIT_IGNORE,
|
||||
PARAM_RESPECT_GEMINI_IGNORE,
|
||||
PARAM_FILE_FILTERING_OPTIONS,
|
||||
PARAM_DESCRIPTION,
|
||||
// Tool-specific parameter names
|
||||
READ_FILE_PARAM_START_LINE,
|
||||
READ_FILE_PARAM_END_LINE,
|
||||
WRITE_FILE_PARAM_CONTENT,
|
||||
GREP_PARAM_INCLUDE_PATTERN,
|
||||
GREP_PARAM_EXCLUDE_PATTERN,
|
||||
GREP_PARAM_NAMES_ONLY,
|
||||
GREP_PARAM_MAX_MATCHES_PER_FILE,
|
||||
GREP_PARAM_TOTAL_MAX_MATCHES,
|
||||
GREP_PARAM_FIXED_STRINGS,
|
||||
GREP_PARAM_CONTEXT,
|
||||
GREP_PARAM_AFTER,
|
||||
GREP_PARAM_BEFORE,
|
||||
GREP_PARAM_NO_IGNORE,
|
||||
EDIT_PARAM_INSTRUCTION,
|
||||
EDIT_PARAM_OLD_STRING,
|
||||
EDIT_PARAM_NEW_STRING,
|
||||
EDIT_PARAM_ALLOW_MULTIPLE,
|
||||
LS_PARAM_IGNORE,
|
||||
SHELL_PARAM_COMMAND,
|
||||
SHELL_PARAM_IS_BACKGROUND,
|
||||
WEB_SEARCH_PARAM_QUERY,
|
||||
WEB_FETCH_PARAM_PROMPT,
|
||||
READ_MANY_PARAM_INCLUDE,
|
||||
READ_MANY_PARAM_EXCLUDE,
|
||||
READ_MANY_PARAM_RECURSIVE,
|
||||
READ_MANY_PARAM_USE_DEFAULT_EXCLUDES,
|
||||
MEMORY_PARAM_FACT,
|
||||
TODOS_PARAM_TODOS,
|
||||
TODOS_ITEM_PARAM_DESCRIPTION,
|
||||
TODOS_ITEM_PARAM_STATUS,
|
||||
DOCS_PARAM_PATH,
|
||||
ASK_USER_PARAM_QUESTIONS,
|
||||
ASK_USER_QUESTION_PARAM_QUESTION,
|
||||
ASK_USER_QUESTION_PARAM_HEADER,
|
||||
ASK_USER_QUESTION_PARAM_TYPE,
|
||||
ASK_USER_QUESTION_PARAM_OPTIONS,
|
||||
ASK_USER_QUESTION_PARAM_MULTI_SELECT,
|
||||
ASK_USER_QUESTION_PARAM_PLACEHOLDER,
|
||||
ASK_USER_OPTION_PARAM_LABEL,
|
||||
ASK_USER_OPTION_PARAM_DESCRIPTION,
|
||||
PLAN_MODE_PARAM_REASON,
|
||||
EXIT_PLAN_PARAM_PLAN_PATH,
|
||||
SKILL_PARAM_NAME,
|
||||
} from './definitions/coreTools.js';
|
||||
|
||||
export {
|
||||
@@ -95,59 +42,6 @@ export {
|
||||
ASK_USER_TOOL_NAME,
|
||||
EXIT_PLAN_MODE_TOOL_NAME,
|
||||
ENTER_PLAN_MODE_TOOL_NAME,
|
||||
// Shared parameter names
|
||||
PARAM_FILE_PATH,
|
||||
PARAM_DIR_PATH,
|
||||
PARAM_PATTERN,
|
||||
PARAM_CASE_SENSITIVE,
|
||||
PARAM_RESPECT_GIT_IGNORE,
|
||||
PARAM_RESPECT_GEMINI_IGNORE,
|
||||
PARAM_FILE_FILTERING_OPTIONS,
|
||||
PARAM_DESCRIPTION,
|
||||
// Tool-specific parameter names
|
||||
READ_FILE_PARAM_START_LINE,
|
||||
READ_FILE_PARAM_END_LINE,
|
||||
WRITE_FILE_PARAM_CONTENT,
|
||||
GREP_PARAM_INCLUDE_PATTERN,
|
||||
GREP_PARAM_EXCLUDE_PATTERN,
|
||||
GREP_PARAM_NAMES_ONLY,
|
||||
GREP_PARAM_MAX_MATCHES_PER_FILE,
|
||||
GREP_PARAM_TOTAL_MAX_MATCHES,
|
||||
GREP_PARAM_FIXED_STRINGS,
|
||||
GREP_PARAM_CONTEXT,
|
||||
GREP_PARAM_AFTER,
|
||||
GREP_PARAM_BEFORE,
|
||||
GREP_PARAM_NO_IGNORE,
|
||||
EDIT_PARAM_INSTRUCTION,
|
||||
EDIT_PARAM_OLD_STRING,
|
||||
EDIT_PARAM_NEW_STRING,
|
||||
EDIT_PARAM_ALLOW_MULTIPLE,
|
||||
LS_PARAM_IGNORE,
|
||||
SHELL_PARAM_COMMAND,
|
||||
SHELL_PARAM_IS_BACKGROUND,
|
||||
WEB_SEARCH_PARAM_QUERY,
|
||||
WEB_FETCH_PARAM_PROMPT,
|
||||
READ_MANY_PARAM_INCLUDE,
|
||||
READ_MANY_PARAM_EXCLUDE,
|
||||
READ_MANY_PARAM_RECURSIVE,
|
||||
READ_MANY_PARAM_USE_DEFAULT_EXCLUDES,
|
||||
MEMORY_PARAM_FACT,
|
||||
TODOS_PARAM_TODOS,
|
||||
TODOS_ITEM_PARAM_DESCRIPTION,
|
||||
TODOS_ITEM_PARAM_STATUS,
|
||||
DOCS_PARAM_PATH,
|
||||
ASK_USER_PARAM_QUESTIONS,
|
||||
ASK_USER_QUESTION_PARAM_QUESTION,
|
||||
ASK_USER_QUESTION_PARAM_HEADER,
|
||||
ASK_USER_QUESTION_PARAM_TYPE,
|
||||
ASK_USER_QUESTION_PARAM_OPTIONS,
|
||||
ASK_USER_QUESTION_PARAM_MULTI_SELECT,
|
||||
ASK_USER_QUESTION_PARAM_PLACEHOLDER,
|
||||
ASK_USER_OPTION_PARAM_LABEL,
|
||||
ASK_USER_OPTION_PARAM_DESCRIPTION,
|
||||
PLAN_MODE_PARAM_REASON,
|
||||
EXIT_PLAN_PARAM_PLAN_PATH,
|
||||
SKILL_PARAM_NAME,
|
||||
};
|
||||
|
||||
export const LS_TOOL_NAME_LEGACY = 'list_directory'; // Just to be safe if anything used the old exported name directly
|
||||
|
||||
@@ -6,3 +6,8 @@
|
||||
|
||||
export const REFERENCE_CONTENT_START = '--- Content from referenced files ---';
|
||||
export const REFERENCE_CONTENT_END = '--- End of content ---';
|
||||
|
||||
export const LOCAL_DEV_SANDBOX_IMAGE_NAME = 'gemini-cli-sandbox';
|
||||
export const SANDBOX_NETWORK_NAME = 'gemini-cli-sandbox';
|
||||
export const SANDBOX_PROXY_NAME = 'gemini-cli-sandbox-proxy';
|
||||
export const GEMINI_DIR = '.gemini';
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { SandboxOrchestrator } from './sandboxOrchestrator.js';
|
||||
import type { SandboxConfig } from '../config/config.js';
|
||||
import { spawnAsync } from './shell-utils.js';
|
||||
|
||||
vi.mock('./shell-utils.js', () => ({
|
||||
spawnAsync: vi.fn(),
|
||||
}));
|
||||
vi.mock('../index.js', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('../index.js')>();
|
||||
return {
|
||||
...actual,
|
||||
debugLogger: {
|
||||
log: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
},
|
||||
coreEvents: {
|
||||
emitFeedback: vi.fn(),
|
||||
},
|
||||
LOCAL_DEV_SANDBOX_IMAGE_NAME: 'gemini-cli-sandbox',
|
||||
};
|
||||
});
|
||||
|
||||
describe('SandboxOrchestrator', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
describe('getContainerRunArgs', () => {
|
||||
it('should build basic run args', async () => {
|
||||
const config: SandboxConfig = {
|
||||
command: 'docker',
|
||||
image: 'some-image',
|
||||
};
|
||||
const args = await SandboxOrchestrator.getContainerRunArgs(
|
||||
config,
|
||||
'/work',
|
||||
'/sandbox',
|
||||
);
|
||||
expect(args).toEqual([
|
||||
'run',
|
||||
'-i',
|
||||
'--rm',
|
||||
'--init',
|
||||
'--workdir',
|
||||
'/sandbox',
|
||||
'-t',
|
||||
'--add-host',
|
||||
'host.docker.internal:host-gateway',
|
||||
'--volume',
|
||||
'/work:/sandbox',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should include flags from config', async () => {
|
||||
const config: SandboxConfig = {
|
||||
command: 'docker',
|
||||
image: 'some-image',
|
||||
flags: '--privileged --net=host',
|
||||
};
|
||||
const args = await SandboxOrchestrator.getContainerRunArgs(
|
||||
config,
|
||||
'/work',
|
||||
'/sandbox',
|
||||
);
|
||||
expect(args).toEqual([
|
||||
'run',
|
||||
'-i',
|
||||
'--rm',
|
||||
'--init',
|
||||
'--workdir',
|
||||
'/sandbox',
|
||||
'--privileged',
|
||||
'--net=host',
|
||||
'-t',
|
||||
'--add-host',
|
||||
'host.docker.internal:host-gateway',
|
||||
'--volume',
|
||||
'/work:/sandbox',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should include flags from arguments if provided', async () => {
|
||||
const config: SandboxConfig = {
|
||||
command: 'docker',
|
||||
image: 'some-image',
|
||||
};
|
||||
const args = await SandboxOrchestrator.getContainerRunArgs(
|
||||
config,
|
||||
'/work',
|
||||
'/sandbox',
|
||||
'--env FOO=bar',
|
||||
);
|
||||
expect(args).toEqual([
|
||||
'run',
|
||||
'-i',
|
||||
'--rm',
|
||||
'--init',
|
||||
'--workdir',
|
||||
'/sandbox',
|
||||
'--env',
|
||||
'FOO=bar',
|
||||
'-t',
|
||||
'--add-host',
|
||||
'host.docker.internal:host-gateway',
|
||||
'--volume',
|
||||
'/work:/sandbox',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should expand environment variables in flags', async () => {
|
||||
vi.stubEnv('TEST_VAR', 'test-value');
|
||||
const config: SandboxConfig = {
|
||||
command: 'docker',
|
||||
image: 'some-image',
|
||||
flags: '--label user=$TEST_VAR',
|
||||
};
|
||||
const args = await SandboxOrchestrator.getContainerRunArgs(
|
||||
config,
|
||||
'/work',
|
||||
'/sandbox',
|
||||
);
|
||||
expect(args).toEqual([
|
||||
'run',
|
||||
'-i',
|
||||
'--rm',
|
||||
'--init',
|
||||
'--workdir',
|
||||
'/sandbox',
|
||||
'--label',
|
||||
'user=test-value',
|
||||
'-t',
|
||||
'--add-host',
|
||||
'host.docker.internal:host-gateway',
|
||||
'--volume',
|
||||
'/work:/sandbox',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle complex quoted flags', async () => {
|
||||
const config: SandboxConfig = {
|
||||
command: 'docker',
|
||||
image: 'some-image',
|
||||
flags: '--env "FOO=bar baz" --label \'key=val with spaces\'',
|
||||
};
|
||||
const args = await SandboxOrchestrator.getContainerRunArgs(
|
||||
config,
|
||||
'/work',
|
||||
'/sandbox',
|
||||
);
|
||||
expect(args).toEqual([
|
||||
'run',
|
||||
'-i',
|
||||
'--rm',
|
||||
'--init',
|
||||
'--workdir',
|
||||
'/sandbox',
|
||||
'--env',
|
||||
'FOO=bar baz',
|
||||
'--label',
|
||||
'key=val with spaces',
|
||||
'-t',
|
||||
'--add-host',
|
||||
'host.docker.internal:host-gateway',
|
||||
'--volume',
|
||||
'/work:/sandbox',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should filter out non-string shell-quote Op objects', async () => {
|
||||
const config: SandboxConfig = {
|
||||
command: 'docker',
|
||||
image: 'some-image',
|
||||
flags: '--flag > /tmp/out', // shell-quote would return { op: '>' }
|
||||
};
|
||||
const args = await SandboxOrchestrator.getContainerRunArgs(
|
||||
config,
|
||||
'/work',
|
||||
'/sandbox',
|
||||
);
|
||||
expect(args).toEqual([
|
||||
'run',
|
||||
'-i',
|
||||
'--rm',
|
||||
'--init',
|
||||
'--workdir',
|
||||
'/sandbox',
|
||||
'--flag',
|
||||
'/tmp/out',
|
||||
'-t',
|
||||
'--add-host',
|
||||
'host.docker.internal:host-gateway',
|
||||
'--volume',
|
||||
'/work:/sandbox',
|
||||
]);
|
||||
// Note: shell-quote filters out the '>' op but keeps the surrounding strings
|
||||
});
|
||||
});
|
||||
|
||||
describe('ensureSandboxImageIsPresent', () => {
|
||||
it('should return true if image exists locally', async () => {
|
||||
vi.mocked(spawnAsync).mockResolvedValueOnce({
|
||||
stdout: 'image-id',
|
||||
stderr: '',
|
||||
});
|
||||
|
||||
const result = await SandboxOrchestrator.ensureSandboxImageIsPresent(
|
||||
'docker',
|
||||
'some-image',
|
||||
);
|
||||
expect(result).toBe(true);
|
||||
expect(spawnAsync).toHaveBeenCalledWith('docker', [
|
||||
'images',
|
||||
'-q',
|
||||
'some-image',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should pull image if missing and return true on success', async () => {
|
||||
// 1. Image check fails (returns empty stdout)
|
||||
vi.mocked(spawnAsync).mockResolvedValueOnce({ stdout: '', stderr: '' });
|
||||
// 2. Pull image succeeds
|
||||
vi.mocked(spawnAsync).mockResolvedValueOnce({
|
||||
stdout: 'Successfully pulled',
|
||||
stderr: '',
|
||||
});
|
||||
// 3. Image check succeeds
|
||||
vi.mocked(spawnAsync).mockResolvedValueOnce({
|
||||
stdout: 'image-id',
|
||||
stderr: '',
|
||||
});
|
||||
|
||||
const result = await SandboxOrchestrator.ensureSandboxImageIsPresent(
|
||||
'docker',
|
||||
'some-image',
|
||||
);
|
||||
expect(result).toBe(true);
|
||||
expect(spawnAsync).toHaveBeenCalledWith('docker', ['pull', 'some-image']);
|
||||
});
|
||||
|
||||
it('should return false if image pull fails', async () => {
|
||||
// 1. Image check fails
|
||||
vi.mocked(spawnAsync).mockResolvedValueOnce({ stdout: '', stderr: '' });
|
||||
// 2. Pull image fails
|
||||
vi.mocked(spawnAsync).mockRejectedValueOnce(new Error('Pull failed'));
|
||||
|
||||
const result = await SandboxOrchestrator.ensureSandboxImageIsPresent(
|
||||
'docker',
|
||||
'some-image',
|
||||
);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { parse } from 'shell-quote';
|
||||
import type { Config, SandboxConfig } from '../config/config.js';
|
||||
import { coreEvents } from './events.js';
|
||||
import { debugLogger } from './debugLogger.js';
|
||||
import { LOCAL_DEV_SANDBOX_IMAGE_NAME } from './constants.js';
|
||||
import { spawnAsync } from './shell-utils.js';
|
||||
|
||||
/**
|
||||
* Orchestrates sandbox image management and command construction.
|
||||
* This class contains non-UI logic for sandboxing.
|
||||
*/
|
||||
export class SandboxOrchestrator {
|
||||
/**
|
||||
* Constructs the arguments for the container engine 'run' command.
|
||||
*/
|
||||
static async getContainerRunArgs(
|
||||
config: SandboxConfig,
|
||||
workdir: string,
|
||||
containerWorkdir: string,
|
||||
sandboxFlags?: string,
|
||||
isPipedInput: boolean = false,
|
||||
): Promise<string[]> {
|
||||
const args = ['run', '-i', '--rm', '--init', '--workdir', containerWorkdir];
|
||||
|
||||
// Priority: env var > settings
|
||||
const flagsToUse = sandboxFlags || config.flags;
|
||||
if (flagsToUse) {
|
||||
const parsedFlags = parse(flagsToUse, process.env).filter(
|
||||
(f): f is string => typeof f === 'string',
|
||||
);
|
||||
args.push(...parsedFlags);
|
||||
}
|
||||
|
||||
if (!isPipedInput) {
|
||||
args.push('-t');
|
||||
}
|
||||
|
||||
// allow access to host.docker.internal
|
||||
args.push('--add-host', 'host.docker.internal:host-gateway');
|
||||
|
||||
// mount current directory as working directory in sandbox
|
||||
args.push('--volume', `${workdir}:${containerWorkdir}`);
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs macOS Seatbelt (sandbox-exec) arguments.
|
||||
*/
|
||||
static getSeatbeltArgs(
|
||||
targetDir: string,
|
||||
tmpDir: string,
|
||||
homeDir: string,
|
||||
cacheDir: string,
|
||||
profileFile: string,
|
||||
includedDirs: string[],
|
||||
maxIncludeDirs: number = 5,
|
||||
): string[] {
|
||||
const args = [
|
||||
'-D',
|
||||
`TARGET_DIR=${targetDir}`,
|
||||
'-D',
|
||||
`TMP_DIR=${tmpDir}`,
|
||||
'-D',
|
||||
`HOME_DIR=${homeDir}`,
|
||||
'-D',
|
||||
`CACHE_DIR=${cacheDir}`,
|
||||
];
|
||||
|
||||
for (let i = 0; i < maxIncludeDirs; i++) {
|
||||
const dirPath = i < includedDirs.length ? includedDirs[i] : '/dev/null';
|
||||
args.push('-D', `INCLUDE_DIR_${i}=${dirPath}`);
|
||||
}
|
||||
|
||||
args.push('-f', profileFile);
|
||||
return args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the sandbox image is present locally or pulled from the registry.
|
||||
*/
|
||||
static async ensureSandboxImageIsPresent(
|
||||
sandbox: string,
|
||||
image: string,
|
||||
cliConfig?: Config,
|
||||
): Promise<boolean> {
|
||||
debugLogger.log(`Checking for sandbox image: ${image}`);
|
||||
if (await this.imageExists(sandbox, image)) {
|
||||
debugLogger.log(`Sandbox image ${image} found locally.`);
|
||||
return true;
|
||||
}
|
||||
|
||||
debugLogger.log(`Sandbox image ${image} not found locally.`);
|
||||
if (image === LOCAL_DEV_SANDBOX_IMAGE_NAME) {
|
||||
// user needs to build the image themselves
|
||||
return false;
|
||||
}
|
||||
|
||||
if (await this.pullImage(sandbox, image, cliConfig)) {
|
||||
// After attempting to pull, check again to be certain
|
||||
if (await this.imageExists(sandbox, image)) {
|
||||
debugLogger.log(
|
||||
`Sandbox image ${image} is now available after pulling.`,
|
||||
);
|
||||
return true;
|
||||
} else {
|
||||
debugLogger.warn(
|
||||
`Sandbox image ${image} still not found after a pull attempt. This might indicate an issue with the image name or registry, or the pull command reported success but failed to make the image available.`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
coreEvents.emitFeedback(
|
||||
'error',
|
||||
`Failed to obtain sandbox image ${image} after check and pull attempt.`,
|
||||
);
|
||||
return false; // Pull command failed or image still not present
|
||||
}
|
||||
|
||||
private static async imageExists(
|
||||
sandbox: string,
|
||||
image: string,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const { stdout } = await spawnAsync(sandbox, ['images', '-q', image]);
|
||||
return stdout.trim() !== '';
|
||||
} catch (err) {
|
||||
debugLogger.warn(
|
||||
`Failed to check image existence with '${sandbox}': ${err instanceof Error ? err.message : String(err)}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static async pullImage(
|
||||
sandbox: string,
|
||||
image: string,
|
||||
cliConfig?: Config,
|
||||
): Promise<boolean> {
|
||||
debugLogger.debug(`Attempting to pull image ${image} using ${sandbox}...`);
|
||||
try {
|
||||
const { stdout } = await spawnAsync(sandbox, ['pull', image]);
|
||||
if (cliConfig?.getDebugMode() || process.env['DEBUG']) {
|
||||
debugLogger.log(stdout.trim());
|
||||
}
|
||||
debugLogger.log(`Successfully pulled image ${image}.`);
|
||||
return true;
|
||||
} catch (err) {
|
||||
debugLogger.warn(
|
||||
`Failed to pull image ${image}: ${err instanceof Error ? err.message : String(err)}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli-devtools",
|
||||
"version": "0.33.0-nightly.20260228.1ca5c05d0",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"main": "dist/src/index.js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli-sdk",
|
||||
"version": "0.33.0-nightly.20260228.1ca5c05d0",
|
||||
"version": "0.29.0-nightly.20260203.71f46f116",
|
||||
"description": "Gemini CLI SDK",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli-test-utils",
|
||||
"version": "0.33.0-nightly.20260228.1ca5c05d0",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"private": true,
|
||||
"main": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "gemini-cli-vscode-ide-companion",
|
||||
"displayName": "Gemini CLI Companion",
|
||||
"description": "Enable Gemini CLI with direct access to your IDE workspace.",
|
||||
"version": "0.33.0-nightly.20260228.1ca5c05d0",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"publisher": "google",
|
||||
"icon": "assets/icon.png",
|
||||
"repository": {
|
||||
|
||||
@@ -1281,6 +1281,13 @@
|
||||
"markdownDescription": "Sandbox execution environment. Set to a boolean to enable or disable the sandbox, or provide a string path to a sandbox profile.\n\n- Category: `Tools`\n- Requires restart: `yes`",
|
||||
"$ref": "#/$defs/BooleanOrString"
|
||||
},
|
||||
"sandboxFlags": {
|
||||
"title": "Sandbox Flags",
|
||||
"description": "Additional flags to pass to the sandbox container engine (Docker or Podman). Environment variables can be used and will be expanded.",
|
||||
"markdownDescription": "Additional flags to pass to the sandbox container engine (Docker or Podman). Environment variables can be used and will be expanded.\n\n- Category: `Tools`\n- Requires restart: `yes`\n- Default: ``",
|
||||
"default": "",
|
||||
"type": "string"
|
||||
},
|
||||
"shell": {
|
||||
"title": "Shell",
|
||||
"description": "Settings for shell execution.",
|
||||
|
||||
Reference in New Issue
Block a user