Compare commits

..

22 Commits

Author SHA1 Message Date
gemini-cli-robot 949f545ec0 chore(release): v0.30.0-test-github-release 2026-02-26 22:07:30 +00:00
Yuna Seol 412721c22c [Do not submit] delete prepare-github-release 2026-02-26 17:03:51 -05:00
Bryan Morgan 9c2fd5a7c6 feat(core): add HTTP 499 to retryable errors and map to RetryableQuotaError (#20432) 2026-02-26 15:42:34 +00:00
Jeffrey Ying 3db35812b7 fix(core): allow /memory add to work in plan mode (#20353) 2026-02-26 13:59:44 +00:00
MD. MOHIBUR RAHMAN 8380f0a3b1 feat(cli): implement interactive shell autocompletion (#20082) 2026-02-26 07:49:11 +00:00
Adib234 ef247e220d feat(plan): support opening and modifying plan in external editor (#20348) 2026-02-26 04:38:44 +00:00
Sandy Tao 39938000a9 feat(core): rename grep_search include parameter to include_pattern (#20328) 2026-02-26 04:16:21 +00:00
Abhijit Balaji d128fb0f7b feat(cli): hide workspace policy update dialog and auto-accept by default (#20351) 2026-02-26 04:05:19 +00:00
gemini-cli-robot 37903d5dbb Changelog for v0.31.0-preview.0 (#20249)
Co-authored-by: gemini-cli-robot <224641728+gemini-cli-robot@users.noreply.github.com>
Co-authored-by: Sam Roberts <158088236+g-samroberts@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: g-samroberts <samroberts@google.com>
2026-02-26 03:18:43 +00:00
Sam Roberts a3821a9440 Update changelog workflow to reject nightly builds (#20248) 2026-02-26 03:12:01 +00:00
gemini-cli-robot 0bf3a015e8 Changelog for v0.30.0 (#20252)
Co-authored-by: g-samroberts <158088236+g-samroberts@users.noreply.github.com>
Co-authored-by: g-samroberts <samroberts@google.com>
2026-02-26 01:11:36 +00:00
Abhi 28c9a907de feat(core): introduce Kind.Agent for sub-agent classification (#20369) 2026-02-26 01:04:20 +00:00
Jacob Richman f9f916e1dc test: support tests that include color information (#20220) 2026-02-25 23:31:35 +00:00
anj-s 78dfe9dea8 feat(core): implement task tracker foundation and service (#19464) 2026-02-25 23:17:17 +00:00
nityam c9d07b62f2 fix: remove trailing comma in issue triage workflow settings json (#20265) 2026-02-25 22:05:21 +00:00
joshualitt 189ab6c637 feat(core): Enable model steering in workspace. (#20343) 2026-02-25 21:18:40 +00:00
Tommaso Sciortino 4aec3cdb24 docs: remove legacy cmd examples in favor of powershell (#20323)
avoid flakes
2026-02-25 12:09:49 -08:00
Achal Jhawar b765fb8af4 fix(cli): expose model.name setting in settings dialog for persistence (#19605) 2026-02-25 19:56:05 +00:00
Himanshu Soni fb0c7d268f fix(cli): skip 404 errors in setup-github file downloads (#20287) 2026-02-25 18:45:02 +00:00
kevinjwang1 be12371380 Plumb the maxAttempts setting through Config args (#20239) 2026-02-25 17:58:07 +00:00
Tommaso Sciortino a037b961b1 feat(cli): load extensions in parallel (#20229) 2026-02-25 17:15:00 +00:00
Adam Weidman 6c739955c0 feat(core): implement robust A2A streaming reassembly and fix task continuity (#20091) 2026-02-25 16:51:08 +00:00
160 changed files with 6839 additions and 1771 deletions
+2 -1
View File
@@ -1,7 +1,8 @@
{
"experimental": {
"plan": true,
"extensionReloading": true
"extensionReloading": true,
"modelSteering": true
},
"general": {
"devtools": true
+19 -6
View File
@@ -59,6 +59,10 @@ To standardize the process of updating changelog files (`latest.md`,
*Use this path if the version number ends in `.0`.*
**Important:** Based on the version, you must choose to follow either section
A.1 for stable releases or A.2 for preview releases. Do not follow the
instructions for the other section.
### A.1: Stable Release (e.g., `v0.28.0`)
For a stable release, you will generate two distinct summaries from the
@@ -73,7 +77,8 @@ detailed **highlights** section for the release-specific page.
use the existing announcements in `docs/changelogs/index.md` and the
example within
`.gemini/skills/docs-changelog/references/index_template.md` as your
guide. This format includes PR links and authors.
guide. This format includes PR links and authors. Stick to 1 or 2 PR
links and authors.
- Add this new announcement to the top of `docs/changelogs/index.md`.
2. **Create Highlights and Update `latest.md`**:
@@ -105,6 +110,10 @@ detailed **highlights** section for the release-specific page.
*Use this path if the version number does **not** end in `.0`.*
**Important:** Based on the version, you must choose to follow either section
B.1 for stable patches or B.2 for preview patches. Do not follow the
instructions for the other section.
### B.1: Stable Patch (e.g., `v0.28.1`)
- **Target File**: `docs/changelogs/latest.md`
@@ -113,10 +122,12 @@ detailed **highlights** section for the release-specific page.
`# Latest stable release: {{version}}`
2. Update the rease date. The line should read,
`Released: {{release_date_month_dd_yyyy}}`
3. **Prepend** the processed "What's Changed" list from the temporary file
3. Determine if a "What's Changed" section exists in the temporary file
If so, continue to step 4. Otherwise, skip to step 5.
4. **Prepend** the processed "What's Changed" list from the temporary file
to the existing "What's Changed" list in `latest.md`. Do not change or
replace the existing list, **only add** to the beginning of it.
4. In the "Full Changelog", edit **only** the end of the URL. Identify the
5. In the "Full Changelog", edit **only** the end of the URL. Identify the
last part of the URL that looks like `...{previous_version}` and update
it to be `...{version}`.
@@ -133,10 +144,12 @@ detailed **highlights** section for the release-specific page.
`# Preview release: {{version}}`
2. Update the rease date. The line should read,
`Released: {{release_date_month_dd_yyyy}}`
3. **Prepend** the processed "What's Changed" list from the temporary file
3. Determine if a "What's Changed" section exists in the temporary file
If so, continue to step 4. Otherwise, skip to step 5.
4. **Prepend** the processed "What's Changed" list from the temporary file
to the existing "What's Changed" list in `preview.md`. Do not change or
replace the existing list, **only add** to the beginning of it.
4. In the "Full Changelog", edit **only** the end of the URL. Identify the
5. In the "Full Changelog", edit **only** the end of the URL. Identify the
last part of the URL that looks like `...{previous_version}` and update
it to be `...{version}`.
@@ -149,5 +162,5 @@ detailed **highlights** section for the release-specific page.
## Finalize
- After making changes, run `npm run format` to ensure consistency.
- After making changes, run `npm run format` ONLY to ensure consistency.
- Delete any temporary files created during the process.
@@ -134,14 +134,6 @@ runs:
run: |
npm run bundle
# TODO: Refactor this github specific publishing script to be generalized based upon inputs.
- name: '📦 Prepare for GitHub release'
if: "inputs.npm-registry-url == 'https://npm.pkg.github.com/'"
working-directory: '${{ inputs.working-directory }}'
shell: 'bash'
run: |
node ${{ github.workspace }}/scripts/prepare-github-release.js
- name: 'Configure npm for publishing to npm'
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020'
with:
@@ -158,7 +158,7 @@ jobs:
},
"coreTools": [
"run_shell_command(echo)"
],
]
}
prompt: |-
## Role
+14
View File
@@ -56,7 +56,18 @@ jobs:
GH_TOKEN: '${{ secrets.GEMINI_CLI_ROBOT_GITHUB_PAT }}'
BODY: '${{ github.event.inputs.body || github.event.release.body }}'
- name: 'Validate version'
id: 'validate_version'
run: |
if echo "${{ steps.release_info.outputs.VERSION }}" | grep -q "nightly"; then
echo "Nightly release detected. Stopping workflow."
echo "CONTINUE=false" >> "$GITHUB_OUTPUT"
else
echo "CONTINUE=true" >> "$GITHUB_OUTPUT"
fi
- name: 'Generate Changelog with Gemini'
if: "steps.validate_version.outputs.CONTINUE == 'true'"
uses: 'google-github-actions/run-gemini-cli@a3bf79042542528e91937b3a3a6fbc4967ee3c31' # ratchet:google-github-actions/run-gemini-cli@v0
with:
gemini_api_key: '${{ secrets.GEMINI_API_KEY }}'
@@ -70,7 +81,10 @@ jobs:
Execute the release notes generation process using the information provided.
When you are done, please output your thought process and the steps you took for future debugging purposes.
- name: 'Create Pull Request'
if: "steps.validate_version.outputs.CONTINUE == 'true'"
uses: 'peter-evans/create-pull-request@v6'
with:
token: '${{ secrets.GEMINI_CLI_ROBOT_GITHUB_PAT }}'
+1 -1
View File
@@ -61,4 +61,4 @@ gemini-debug.log
.genkit
.gemini-clipboard/
.eslintcache
evals/logs/
evals/logs/
+22
View File
@@ -18,6 +18,28 @@ on GitHub.
| [Preview](preview.md) | Experimental features ready for early feedback. |
| [Stable](latest.md) | Stable, recommended for general use. |
## Announcements: v0.30.0 - 2026-02-25
- **SDK & Custom Skills:** Introduced the initial SDK package, enabling dynamic
system instructions, `SessionContext` for SDK tool calls, and support for
custom skills
([#18861](https://github.com/google-gemini/gemini-cli/pull/18861) by
@mbleigh).
- **Policy Engine Enhancements:** Added a new `--policy` flag for user-defined
policies, introduced strict seatbelt profiles, and deprecated
`--allowed-tools` in favor of the policy engine
([#18500](https://github.com/google-gemini/gemini-cli/pull/18500) by
@allenhutchison).
- **UI & Themes:** Added a generic searchable list for settings and extensions,
new Solarized themes, text wrapping for markdown tables, and a clean UI toggle
prototype ([#19064](https://github.com/google-gemini/gemini-cli/pull/19064) by
@rmedranollamas).
- **Vim & Terminal Interaction:** Improved Vim support to feel more complete and
added support for Ctrl-Z terminal suspension
([#18755](https://github.com/google-gemini/gemini-cli/pull/18755) by
@ppgranger, [#18931](https://github.com/google-gemini/gemini-cli/pull/18931)
by @scidomino).
## Announcements: v0.29.0 - 2026-02-17
- **Plan Mode:** A new comprehensive planning capability with `/plan`,
+309 -357
View File
@@ -1,6 +1,6 @@
# Latest stable release: v0.29.0
# Latest stable release: v0.30.0
Released: February 17, 2026
Released: February 25, 2026
For most users, our latest stable release is the recommended release. Install
the latest stable version with:
@@ -11,371 +11,323 @@ npm install -g @google/gemini-cli
## Highlights
- **Plan Mode:** Introduce a dedicated "Plan Mode" to help you architect complex
changes before implementation. Use `/plan` to get started.
- **Gemini 3 by Default:** Gemini 3 is now the default model family, bringing
improved performance and reasoning capabilities to all users without needing a
feature flag.
- **Extension Discovery:** Easily discover and install extensions with the new
exploration features and registry client.
- **Enhanced Admin Controls:** New administrative capabilities allow for
allowlisting MCP server configurations, giving organizations more control over
available tools.
- **Sub-agent Improvements:** Sub-agents have been transitioned to a new format
with improved definitions and system prompts for better reliability.
- **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
- fix: remove `ask_user` tool from non-interactive modes by @jackwotherspoon in
[#18154](https://github.com/google-gemini/gemini-cli/pull/18154)
- fix(cli): allow restricted .env loading in untrusted sandboxed folders by
@galz10 in [#17806](https://github.com/google-gemini/gemini-cli/pull/17806)
- Encourage agent to utilize ecosystem tools to perform work by @gundermanc in
[#17881](https://github.com/google-gemini/gemini-cli/pull/17881)
- feat(plan): unify workflow location in system prompt to optimize caching by
@jerop in [#18258](https://github.com/google-gemini/gemini-cli/pull/18258)
- feat(core): enable getUserTierName in config by @sehoon38 in
[#18265](https://github.com/google-gemini/gemini-cli/pull/18265)
- feat(core): add default execution limits for subagents by @abhipatel12 in
[#18274](https://github.com/google-gemini/gemini-cli/pull/18274)
- Fix issue where agent gets stuck at interactive commands. by @gundermanc in
[#18272](https://github.com/google-gemini/gemini-cli/pull/18272)
- chore(release): bump version to 0.29.0-nightly.20260203.71f46f116 by
- 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
[#18243](https://github.com/google-gemini/gemini-cli/pull/18243)
- feat(core): remove hardcoded policy bypass for local subagents by @abhipatel12
in [#18153](https://github.com/google-gemini/gemini-cli/pull/18153)
- feat(plan): implement `plan` slash command by @Adib234 in
[#17698](https://github.com/google-gemini/gemini-cli/pull/17698)
- feat: increase `ask_user` label limit to 16 characters by @jackwotherspoon in
[#18320](https://github.com/google-gemini/gemini-cli/pull/18320)
- Add information about the agent skills lifecycle and clarify docs-writer skill
metadata. by @g-samroberts in
[#18234](https://github.com/google-gemini/gemini-cli/pull/18234)
- feat(core): add `enter_plan_mode` tool by @jerop in
[#18324](https://github.com/google-gemini/gemini-cli/pull/18324)
- Stop showing an error message in `/plan` by @Adib234 in
[#18333](https://github.com/google-gemini/gemini-cli/pull/18333)
- fix(hooks): remove unnecessary logging for hook registration by @abhipatel12
in [#18332](https://github.com/google-gemini/gemini-cli/pull/18332)
- fix(mcp): ensure MCP transport is closed to prevent memory leaks by
@cbcoutinho in
[#18054](https://github.com/google-gemini/gemini-cli/pull/18054)
- feat(skills): implement linking for agent skills by @MushuEE in
[#18295](https://github.com/google-gemini/gemini-cli/pull/18295)
- Changelogs for 0.27.0 and 0.28.0-preview0 by @g-samroberts in
[#18336](https://github.com/google-gemini/gemini-cli/pull/18336)
- chore: correct docs as skills and hooks are stable by @jackwotherspoon in
[#18358](https://github.com/google-gemini/gemini-cli/pull/18358)
- feat(admin): Implement admin allowlist for MCP server configurations by
@skeshive in [#18311](https://github.com/google-gemini/gemini-cli/pull/18311)
- fix(core): add retry logic for transient SSL/TLS errors (#17318) by @ppgranger
in [#18310](https://github.com/google-gemini/gemini-cli/pull/18310)
- Add support for /extensions config command by @chrstnb in
[#17895](https://github.com/google-gemini/gemini-cli/pull/17895)
- fix(core): handle non-compliant mcpbridge responses from Xcode 26.3 by
@peterfriese in
[#18376](https://github.com/google-gemini/gemini-cli/pull/18376)
- feat(cli): Add W, B, E Vim motions and operator support by @ademuri in
[#16209](https://github.com/google-gemini/gemini-cli/pull/16209)
- fix: Windows Specific Agent Quality & System Prompt by @scidomino in
[#18351](https://github.com/google-gemini/gemini-cli/pull/18351)
- feat(plan): support `replace` tool in plan mode to edit plans by @jerop in
[#18379](https://github.com/google-gemini/gemini-cli/pull/18379)
- Improving memory tool instructions and eval testing by @alisa-alisa in
[#18091](https://github.com/google-gemini/gemini-cli/pull/18091)
- fix(cli): color extension link success message green by @MushuEE in
[#18386](https://github.com/google-gemini/gemini-cli/pull/18386)
- undo by @jacob314 in
[#18147](https://github.com/google-gemini/gemini-cli/pull/18147)
- feat(plan): add guidance on iterating on approved plans vs creating new plans
by @jerop in [#18346](https://github.com/google-gemini/gemini-cli/pull/18346)
- feat(plan): fix invalid tool calls in plan mode by @Adib234 in
[#18352](https://github.com/google-gemini/gemini-cli/pull/18352)
- feat(plan): integrate planning artifacts and tools into primary workflows by
@jerop in [#18375](https://github.com/google-gemini/gemini-cli/pull/18375)
- Fix permission check by @scidomino in
[#18395](https://github.com/google-gemini/gemini-cli/pull/18395)
- ux(polish) autocomplete in the input prompt by @jacob314 in
[#18181](https://github.com/google-gemini/gemini-cli/pull/18181)
- fix: resolve infinite loop when using 'Modify with external editor' by
@ppgranger in [#17453](https://github.com/google-gemini/gemini-cli/pull/17453)
- feat: expand verify-release to macOS and Windows by @yunaseoul in
[#18145](https://github.com/google-gemini/gemini-cli/pull/18145)
- feat(plan): implement support for MCP servers in Plan mode by @Adib234 in
[#18229](https://github.com/google-gemini/gemini-cli/pull/18229)
- chore: update folder trust error messaging by @galz10 in
[#18402](https://github.com/google-gemini/gemini-cli/pull/18402)
- feat(plan): create a metric for execution of plans generated in plan mode by
@Adib234 in [#18236](https://github.com/google-gemini/gemini-cli/pull/18236)
- perf(ui): optimize stripUnsafeCharacters with regex by @gsquared94 in
[#18413](https://github.com/google-gemini/gemini-cli/pull/18413)
- feat(context): implement observation masking for tool outputs by @abhipatel12
in [#18389](https://github.com/google-gemini/gemini-cli/pull/18389)
- feat(core,cli): implement session-linked tool output storage and cleanup by
@abhipatel12 in
[#18416](https://github.com/google-gemini/gemini-cli/pull/18416)
- Shorten temp directory by @joshualitt in
[#17901](https://github.com/google-gemini/gemini-cli/pull/17901)
- feat(plan): add behavioral evals for plan mode by @jerop in
[#18437](https://github.com/google-gemini/gemini-cli/pull/18437)
- Add extension registry client by @chrstnb in
[#18396](https://github.com/google-gemini/gemini-cli/pull/18396)
- Enable extension config by default by @chrstnb in
[#18447](https://github.com/google-gemini/gemini-cli/pull/18447)
- Automatically generate change logs on release by @g-samroberts in
[#18401](https://github.com/google-gemini/gemini-cli/pull/18401)
- Remove previewFeatures and default to Gemini 3 by @sehoon38 in
[#18414](https://github.com/google-gemini/gemini-cli/pull/18414)
- feat(admin): apply MCP allowlist to extensions & gemini mcp list command by
@skeshive in [#18442](https://github.com/google-gemini/gemini-cli/pull/18442)
- fix(cli): improve focus navigation for interactive and background shells by
@galz10 in [#18343](https://github.com/google-gemini/gemini-cli/pull/18343)
- Add shortcuts hint and panel for discoverability by @LyalinDotCom in
[#18035](https://github.com/google-gemini/gemini-cli/pull/18035)
- fix(config): treat system settings as read-only during migration and warn user
by @spencer426 in
[#18277](https://github.com/google-gemini/gemini-cli/pull/18277)
- feat(plan): add positive test case and update eval stability policy by @jerop
in [#18457](https://github.com/google-gemini/gemini-cli/pull/18457)
- fix- windows: add shell: true for spawnSync to fix EINVAL with .cmd editors by
@zackoch in [#18408](https://github.com/google-gemini/gemini-cli/pull/18408)
- bug(core): Fix bug when saving plans. by @joshualitt in
[#18465](https://github.com/google-gemini/gemini-cli/pull/18465)
- Refactor atCommandProcessor by @scidomino in
[#18461](https://github.com/google-gemini/gemini-cli/pull/18461)
- feat(core): implement persistence and resumption for masked tool outputs by
@abhipatel12 in
[#18451](https://github.com/google-gemini/gemini-cli/pull/18451)
- refactor: simplify tool output truncation to single config by @SandyTao520 in
[#18446](https://github.com/google-gemini/gemini-cli/pull/18446)
- bug(core): Ensure storage is initialized early, even if config is not. by
@joshualitt in
[#18471](https://github.com/google-gemini/gemini-cli/pull/18471)
- chore: Update build-and-start script to support argument forwarding by
@Abhijit-2592 in
[#18241](https://github.com/google-gemini/gemini-cli/pull/18241)
- fix(core): prevent subagent bypass in plan mode by @jerop in
[#18484](https://github.com/google-gemini/gemini-cli/pull/18484)
- feat(cli): add WebSocket-based network logging and streaming chunk support by
@SandyTao520 in
[#18383](https://github.com/google-gemini/gemini-cli/pull/18383)
- feat(cli): update approval modes UI by @jerop in
[#18476](https://github.com/google-gemini/gemini-cli/pull/18476)
- fix(cli): reload skills and agents on extension restart by @NTaylorMullen in
[#18411](https://github.com/google-gemini/gemini-cli/pull/18411)
- fix(core): expand excludeTools with legacy aliases for renamed tools by
@SandyTao520 in
[#18498](https://github.com/google-gemini/gemini-cli/pull/18498)
- feat(core): overhaul system prompt for rigor, integrity, and intent alignment
by @NTaylorMullen in
[#17263](https://github.com/google-gemini/gemini-cli/pull/17263)
- Patch for generate changelog docs yaml file by @g-samroberts in
[#18496](https://github.com/google-gemini/gemini-cli/pull/18496)
- Code review fixes for show question mark pr. by @jacob314 in
[#18480](https://github.com/google-gemini/gemini-cli/pull/18480)
- fix(cli): add SS3 Shift+Tab support for Windows terminals by @ThanhNguyxn in
[#18187](https://github.com/google-gemini/gemini-cli/pull/18187)
- chore: remove redundant planning prompt from final shell by @jerop in
[#18528](https://github.com/google-gemini/gemini-cli/pull/18528)
- docs: require pr-creator skill for PR generation by @NTaylorMullen in
[#18536](https://github.com/google-gemini/gemini-cli/pull/18536)
- chore: update colors for ask_user dialog by @jackwotherspoon in
[#18543](https://github.com/google-gemini/gemini-cli/pull/18543)
- feat(core): exempt high-signal tools from output masking by @abhipatel12 in
[#18545](https://github.com/google-gemini/gemini-cli/pull/18545)
- refactor(core): remove memory tool instructions from Gemini 3 prompt by
@NTaylorMullen in
[#18559](https://github.com/google-gemini/gemini-cli/pull/18559)
- chore: remove feedback instruction from system prompt by @NTaylorMullen in
[#18560](https://github.com/google-gemini/gemini-cli/pull/18560)
- feat(context): add remote configuration for tool output masking thresholds by
@abhipatel12 in
[#18553](https://github.com/google-gemini/gemini-cli/pull/18553)
- feat(core): pause agent timeout budget while waiting for tool confirmation by
@abhipatel12 in
[#18415](https://github.com/google-gemini/gemini-cli/pull/18415)
- refactor(config): remove experimental.enableEventDrivenScheduler setting by
@abhipatel12 in
[#17924](https://github.com/google-gemini/gemini-cli/pull/17924)
- feat(cli): truncate shell output in UI history and improve active shell
display by @jwhelangoog in
[#17438](https://github.com/google-gemini/gemini-cli/pull/17438)
- refactor(cli): switch useToolScheduler to event-driven engine by @abhipatel12
in [#18565](https://github.com/google-gemini/gemini-cli/pull/18565)
- fix(core): correct escaped interpolation in system prompt by @NTaylorMullen in
[#18557](https://github.com/google-gemini/gemini-cli/pull/18557)
- propagate abortSignal by @scidomino in
[#18477](https://github.com/google-gemini/gemini-cli/pull/18477)
- feat(core): conditionally include ctrl+f prompt based on interactive shell
setting by @NTaylorMullen in
[#18561](https://github.com/google-gemini/gemini-cli/pull/18561)
- fix(core): ensure `enter_plan_mode` tool registration respects
`experimental.plan` by @jerop in
[#18587](https://github.com/google-gemini/gemini-cli/pull/18587)
- feat(core): transition sub-agents to XML format and improve definitions by
@NTaylorMullen in
[#18555](https://github.com/google-gemini/gemini-cli/pull/18555)
- docs: Add Plan Mode documentation by @jerop in
[#18582](https://github.com/google-gemini/gemini-cli/pull/18582)
- chore: strengthen validation guidance in system prompt by @NTaylorMullen in
[#18544](https://github.com/google-gemini/gemini-cli/pull/18544)
- Fix newline insertion bug in replace tool by @werdnum in
[#18595](https://github.com/google-gemini/gemini-cli/pull/18595)
- fix(evals): update save_memory evals and simplify tool description by
@NTaylorMullen in
[#18610](https://github.com/google-gemini/gemini-cli/pull/18610)
- chore(evals): update validation_fidelity_pre_existing_errors to USUALLY_PASSES
by @NTaylorMullen in
[#18617](https://github.com/google-gemini/gemini-cli/pull/18617)
- fix: shorten tool call IDs and fix duplicate tool name in truncated output
filenames by @SandyTao520 in
[#18600](https://github.com/google-gemini/gemini-cli/pull/18600)
- feat(cli): implement atomic writes and safety checks for trusted folders by
@galz10 in [#18406](https://github.com/google-gemini/gemini-cli/pull/18406)
- Remove relative docs links by @chrstnb in
[#18650](https://github.com/google-gemini/gemini-cli/pull/18650)
- docs: add legacy snippets convention to GEMINI.md by @NTaylorMullen in
[#18597](https://github.com/google-gemini/gemini-cli/pull/18597)
- fix(chore): Support linting for cjs by @aswinashok44 in
[#18639](https://github.com/google-gemini/gemini-cli/pull/18639)
- feat: move shell efficiency guidelines to tool description by @NTaylorMullen
in [#18614](https://github.com/google-gemini/gemini-cli/pull/18614)
- Added "" as default value, since getText() used to expect a string only and
thus crashed when undefined... Fixes #18076 by @019-Abhi in
[#18099](https://github.com/google-gemini/gemini-cli/pull/18099)
- Allow @-includes outside of workspaces (with permission) by @scidomino in
[#18470](https://github.com/google-gemini/gemini-cli/pull/18470)
- chore: make `ask_user` header description more clear by @jackwotherspoon in
[#18657](https://github.com/google-gemini/gemini-cli/pull/18657)
- refactor(core): model-dependent tool definitions by @aishaneeshah in
[#18563](https://github.com/google-gemini/gemini-cli/pull/18563)
- Harded code assist converter. by @jacob314 in
[#18656](https://github.com/google-gemini/gemini-cli/pull/18656)
- bug(core): Fix minor bug in migration logic. by @joshualitt in
[#18661](https://github.com/google-gemini/gemini-cli/pull/18661)
- feat: enable plan mode experiment in settings by @jerop in
[#18636](https://github.com/google-gemini/gemini-cli/pull/18636)
- refactor: push isValidPath() into parsePastedPaths() by @scidomino in
[#18664](https://github.com/google-gemini/gemini-cli/pull/18664)
- fix(cli): correct 'esc to cancel' position and restore duration display by
@NTaylorMullen in
[#18534](https://github.com/google-gemini/gemini-cli/pull/18534)
- feat(cli): add DevTools integration with gemini-cli-devtools by @SandyTao520
in [#18648](https://github.com/google-gemini/gemini-cli/pull/18648)
- chore: remove unused exports and redundant hook files by @SandyTao520 in
[#18681](https://github.com/google-gemini/gemini-cli/pull/18681)
- Fix number of lines being reported in rewind confirmation dialog by @Adib234
in [#18675](https://github.com/google-gemini/gemini-cli/pull/18675)
- feat(cli): disable folder trust in headless mode by @galz10 in
[#18407](https://github.com/google-gemini/gemini-cli/pull/18407)
- Disallow unsafe type assertions by @gundermanc in
[#18688](https://github.com/google-gemini/gemini-cli/pull/18688)
- Change event type for release by @g-samroberts in
[#18693](https://github.com/google-gemini/gemini-cli/pull/18693)
- feat: handle multiple dynamic context filenames in system prompt by
@NTaylorMullen in
[#18598](https://github.com/google-gemini/gemini-cli/pull/18598)
- Properly parse at-commands with narrow non-breaking spaces by @scidomino in
[#18677](https://github.com/google-gemini/gemini-cli/pull/18677)
- refactor(core): centralize core tool definitions and support model-specific
schemas by @aishaneeshah in
[#18662](https://github.com/google-gemini/gemini-cli/pull/18662)
- feat(core): Render memory hierarchically in context. by @joshualitt in
[#18350](https://github.com/google-gemini/gemini-cli/pull/18350)
- feat: Ctrl+O to expand paste placeholder by @jackwotherspoon in
[#18103](https://github.com/google-gemini/gemini-cli/pull/18103)
- fix(cli): Improve header spacing by @NTaylorMullen in
[#18531](https://github.com/google-gemini/gemini-cli/pull/18531)
- Feature/quota visibility 16795 by @spencer426 in
[#18203](https://github.com/google-gemini/gemini-cli/pull/18203)
- Inline thinking bubbles with summary/full modes by @LyalinDotCom in
[#18033](https://github.com/google-gemini/gemini-cli/pull/18033)
- docs: remove TOC marker from Plan Mode header by @jerop in
[#18678](https://github.com/google-gemini/gemini-cli/pull/18678)
- fix(ui): remove redundant newlines in Gemini messages by @NTaylorMullen in
[#18538](https://github.com/google-gemini/gemini-cli/pull/18538)
- test(cli): fix AppContainer act() warnings and improve waitFor resilience by
@NTaylorMullen in
[#18676](https://github.com/google-gemini/gemini-cli/pull/18676)
- refactor(core): refine Security & System Integrity section in system prompt by
@NTaylorMullen in
[#18601](https://github.com/google-gemini/gemini-cli/pull/18601)
- Fix layout rounding. by @gundermanc in
[#18667](https://github.com/google-gemini/gemini-cli/pull/18667)
- docs(skills): enhance pr-creator safety and interactivity by @NTaylorMullen in
[#18616](https://github.com/google-gemini/gemini-cli/pull/18616)
- test(core): remove hardcoded model from TestRig by @NTaylorMullen in
[#18710](https://github.com/google-gemini/gemini-cli/pull/18710)
- feat(core): optimize sub-agents system prompt intro by @NTaylorMullen in
[#18608](https://github.com/google-gemini/gemini-cli/pull/18608)
- feat(cli): update approval mode labels and shortcuts per latest UX spec by
@jerop in [#18698](https://github.com/google-gemini/gemini-cli/pull/18698)
- fix(plan): update persistent approval mode setting by @Adib234 in
[#18638](https://github.com/google-gemini/gemini-cli/pull/18638)
- fix: move toasts location to left side by @jackwotherspoon in
[#18705](https://github.com/google-gemini/gemini-cli/pull/18705)
- feat(routing): restrict numerical routing to Gemini 3 family by @mattKorwel in
[#18478](https://github.com/google-gemini/gemini-cli/pull/18478)
- fix(ide): fix ide nudge setting by @skeshive in
[#18733](https://github.com/google-gemini/gemini-cli/pull/18733)
- fix(core): standardize tool formatting in system prompts by @NTaylorMullen in
[#18615](https://github.com/google-gemini/gemini-cli/pull/18615)
- chore: consolidate to green in ask user dialog by @jackwotherspoon in
[#18734](https://github.com/google-gemini/gemini-cli/pull/18734)
- feat: add `extensionsExplore` setting to enable extensions explore UI. by
@sripasg in [#18686](https://github.com/google-gemini/gemini-cli/pull/18686)
- feat(cli): defer devtools startup and integrate with F12 by @SandyTao520 in
[#18695](https://github.com/google-gemini/gemini-cli/pull/18695)
- ui: update & subdue footer colors and animate progress indicator by
@keithguerin in
[#18570](https://github.com/google-gemini/gemini-cli/pull/18570)
- test: add model-specific snapshots for coreTools by @aishaneeshah in
[#18707](https://github.com/google-gemini/gemini-cli/pull/18707)
- ci: shard windows tests and fix event listener leaks by @NTaylorMullen in
[#18670](https://github.com/google-gemini/gemini-cli/pull/18670)
- fix: allow `ask_user` tool in yolo mode by @jackwotherspoon in
[#18541](https://github.com/google-gemini/gemini-cli/pull/18541)
- feat: redact disabled tools from system prompt (#13597) by @NTaylorMullen in
[#18613](https://github.com/google-gemini/gemini-cli/pull/18613)
- Update Gemini.md to use the curent year on creating new files by @sehoon38 in
[#18460](https://github.com/google-gemini/gemini-cli/pull/18460)
- Code review cleanup for thinking display by @jacob314 in
[#18720](https://github.com/google-gemini/gemini-cli/pull/18720)
- fix(cli): hide scrollbars when in alternate buffer copy mode by @werdnum in
[#18354](https://github.com/google-gemini/gemini-cli/pull/18354)
- Fix issues with rip grep by @gundermanc in
[#18756](https://github.com/google-gemini/gemini-cli/pull/18756)
- fix(cli): fix history navigation regression after prompt autocomplete by
@sehoon38 in [#18752](https://github.com/google-gemini/gemini-cli/pull/18752)
- chore: cleanup unused and add unlisted dependencies in packages/cli by
[#18772](https://github.com/google-gemini/gemini-cli/pull/18772)
- chore: cleanup unused and add unlisted dependencies in packages/core by
@adamfweidman in
[#18749](https://github.com/google-gemini/gemini-cli/pull/18749)
- Fix issue where Gemini CLI creates tests in a new file by @gundermanc in
[#18409](https://github.com/google-gemini/gemini-cli/pull/18409)
- feat(telemetry): Ensure experiment IDs are included in OpenTelemetry logs by
[#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
[#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
[#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
[#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
[#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
[#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
[#18747](https://github.com/google-gemini/gemini-cli/pull/18747)
- fix(patch): cherry-pick e9a9474 to release/v0.29.0-preview.0-pr-18840 to patch
version v0.29.0-preview.0 and create version 0.29.0-preview.1 by
[#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
[#18841](https://github.com/google-gemini/gemini-cli/pull/18841)
- fix(patch): cherry-pick 08e8eea to release/v0.29.0-preview.1-pr-18855 to patch
version v0.29.0-preview.1 and create version 0.29.0-preview.2 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
[#18905](https://github.com/google-gemini/gemini-cli/pull/18905)
- fix(patch): cherry-pick d0c6a56 to release/v0.29.0-preview.2-pr-18976 to patch
version v0.29.0-preview.2 and create version 0.29.0-preview.3 by
[#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
[#19023](https://github.com/google-gemini/gemini-cli/pull/19023)
- fix(patch): cherry-pick e5ff202 to release/v0.29.0-preview.3-pr-19254 to patch
version v0.29.0-preview.3 and create version 0.29.0-preview.4 by
[#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
[#19264](https://github.com/google-gemini/gemini-cli/pull/19264)
- fix(patch): cherry-pick 9590a09 to release/v0.29.0-preview.4-pr-18771 to patch
version v0.29.0-preview.4 and create version 0.29.0-preview.5 by
[#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
[#19274](https://github.com/google-gemini/gemini-cli/pull/19274)
[#20112](https://github.com/google-gemini/gemini-cli/pull/20112)
**Full Changelog**:
https://github.com/google-gemini/gemini-cli/compare/v0.28.2...v0.29.0
https://github.com/google-gemini/gemini-cli/compare/v0.29.7...v0.30.0
+388 -294
View File
@@ -1,6 +1,6 @@
# Preview release: v0.30.0-preview.5
# Preview release: v0.31.0-preview.0
Released: February 24, 2026
Released: February 25, 2026
Our preview release includes the latest, new, and experimental features. This
release may not be as stable as our [latest weekly release](latest.md).
@@ -13,306 +13,400 @@ npm install -g @google/gemini-cli@preview
## Highlights
- **Initial SDK Package:** Introduced the initial SDK package with support for
custom skills and dynamic system instructions.
- **Refined Plan Mode:** Refined Plan Mode with support for enabling skills,
improved agentic execution, and project exploration without planning.
- **Enhanced CLI UI:** Enhanced CLI UI with a new clean UI toggle, minimal-mode
bleed-through, and support for Ctrl-Z suspension.
- **`--policy` flag:** Added the `--policy` flag to support user-defined
policies.
- **New Themes:** Added Solarized Dark and Solarized Light themes.
- **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
- 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 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
[#19490](https://github.com/google-gemini/gemini-cli/pull/19490)
- 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
- 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
[#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
[#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
[#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
[#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
@SandyTao520 in
[#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
[#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
[#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
[#18882](https://github.com/google-gemini/gemini-cli/pull/18882)
- feat(core): refine Plan Mode system prompt for agentic execution by
[#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
[#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
[#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
[#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
[#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
[#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)
[#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.29.0-preview.5...v0.30.0-preview.5
**Full Changelog**:
https://github.com/google-gemini/gemini-cli/compare/v0.30.0-preview.6...v0.31.0-preview.0
+2
View File
@@ -119,6 +119,7 @@ These are the only allowed tools:
- **Planning (Write):** [`write_file`] and [`replace`] only allowed for `.md`
files in the `~/.gemini/tmp/<project>/<session-id>/plans/` directory or your
[custom plans directory](#custom-plan-directory-and-policies).
- **Memory:** [`save_memory`]
- **Skills:** [`activate_skill`] (allows loading specialized instructions and
resources in a read-only manner)
@@ -277,6 +278,7 @@ performance. You can disable this automatic switching in your settings:
[`google_web_search`]: /docs/tools/web-search.md
[`replace`]: /docs/tools/file-system.md#6-replace-edit
[MCP tools]: /docs/tools/mcp-server.md
[`save_memory`]: /docs/tools/memory.md
[`activate_skill`]: /docs/cli/skills.md
[subagents]: /docs/core/subagents.md
[policy engine]: /docs/reference/policy-engine.md
+7 -6
View File
@@ -82,12 +82,13 @@ they appear in the UI.
### Model
| UI Label | Setting | Description | Default |
| ----------------------- | ---------------------------- | -------------------------------------------------------------------------------------- | ------- |
| Max Session Turns | `model.maxSessionTurns` | Maximum number of user/model/tool turns to keep in a session. -1 means unlimited. | `-1` |
| Compression Threshold | `model.compressionThreshold` | The fraction of context usage at which to trigger context compression (e.g. 0.2, 0.3). | `0.5` |
| Disable Loop Detection | `model.disableLoopDetection` | Disable automatic detection and prevention of infinite loops. | `false` |
| Skip Next Speaker Check | `model.skipNextSpeakerCheck` | Skip the next speaker check. | `true` |
| UI Label | Setting | Description | Default |
| ----------------------- | ---------------------------- | -------------------------------------------------------------------------------------- | ----------- |
| Model | `model.name` | The Gemini model to use for conversations. | `undefined` |
| Max Session Turns | `model.maxSessionTurns` | Maximum number of user/model/tool turns to keep in a session. -1 means unlimited. | `-1` |
| Compression Threshold | `model.compressionThreshold` | The fraction of context usage at which to trigger context compression (e.g. 0.2, 0.3). | `0.5` |
| Disable Loop Detection | `model.disableLoopDetection` | Disable automatic detection and prevention of infinite loops. | `false` |
| Skip Next Speaker Check | `model.skipNextSpeakerCheck` | Skip the next speaker check. | `true` |
### Context
+1 -10
View File
@@ -19,16 +19,7 @@ can find your npm cache path by running `npm config get cache`.
rm -rf "$(npm config get cache)/_npx"
```
**For Windows**
_Command Prompt_
```cmd
:: The path is typically %LocalAppData%\npm-cache\_npx
rmdir /s /q "%LocalAppData%\npm-cache\_npx"
```
_PowerShell_
**For Windows (PowerShell)**
```powershell
# The path is typically $env:LocalAppData\npm-cache\_npx
+4 -4
View File
@@ -93,7 +93,7 @@ describe('grep_search_functionality', () => {
});
evalTest('USUALLY_PASSES', {
name: 'should search only within the specified include glob',
name: 'should search only within the specified include_pattern glob',
files: {
'file.js': 'my_function();',
'file.ts': 'my_function();',
@@ -105,19 +105,19 @@ describe('grep_search_functionality', () => {
undefined,
(args) => {
const params = JSON.parse(args);
return params.include === '*.js';
return params.include_pattern === '*.js';
},
);
expect(
wasToolCalled,
'Expected grep_search to be called with include: "*.js"',
'Expected grep_search to be called with include_pattern: "*.js"',
).toBe(true);
assertModelHasOutput(result);
checkModelOutputContent(result, {
expectedContent: [/file.js/],
forbiddenContent: [/file.ts/],
testName: `${TEST_PREFIX}include glob search`,
testName: `${TEST_PREFIX}include_pattern glob search`,
});
},
});
+2 -2
View File
@@ -55,8 +55,8 @@ describe('file-system', () => {
});
});
it('should be able to write a file', async () => {
await rig.setup('should be able to write a file', {
it('should be able to write a hello world message to a file', async () => {
await rig.setup('should be able to write a hello world message to a file', {
settings: { tools: { core: ['write_file', 'replace', 'read_file'] } },
});
rig.createFile('test.txt', '');
+4 -1
View File
@@ -102,7 +102,10 @@ describe('ripgrep-real-direct', () => {
'console.log("hello");\n',
);
const invocation = tool.build({ pattern: 'hello', include: '*.js' });
const invocation = tool.build({
pattern: 'hello',
include_pattern: '*.js',
});
const result = await invocation.execute(new AbortController().signal);
expect(result.llmContent).toContain('Found 1 match');
+2 -2
View File
@@ -22,8 +22,8 @@ describe('write_file', () => {
afterEach(async () => await rig.cleanup());
it('should be able to write a file', async () => {
await rig.setup('should be able to write a file', {
it('should be able to write a joke to a file', async () => {
await rig.setup('should be able to write a joke to a file', {
settings: { tools: { core: ['write_file', 'read_file'] } },
});
const prompt = `show me an example of using the write tool. put a dad joke in dad.txt`;
+10 -34
View File
@@ -1,12 +1,12 @@
{
"name": "@google/gemini-cli",
"version": "0.30.0-nightly.20260210.a2174751d",
"version": "0.30.0-test-github-release",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@google/gemini-cli",
"version": "0.30.0-nightly.20260210.a2174751d",
"version": "0.30.0-test-github-release",
"workspaces": [
"packages/*"
],
@@ -2292,7 +2292,6 @@
"integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@octokit/auth-token": "^6.0.0",
"@octokit/graphql": "^9.0.2",
@@ -2473,7 +2472,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">=8.0.0"
}
@@ -2523,7 +2521,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz",
"integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/semantic-conventions": "^1.29.0"
},
@@ -2898,7 +2895,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.5.0.tgz",
"integrity": "sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/core": "2.5.0",
"@opentelemetry/semantic-conventions": "^1.29.0"
@@ -2932,7 +2928,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.5.0.tgz",
"integrity": "sha512-BeJLtU+f5Gf905cJX9vXFQorAr6TAfK3SPvTFqP+scfIpDQEJfRaGJWta7sJgP+m4dNtBf9y3yvBKVAZZtJQVA==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/core": "2.5.0",
"@opentelemetry/resources": "2.5.0"
@@ -2987,7 +2982,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.5.0.tgz",
"integrity": "sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/core": "2.5.0",
"@opentelemetry/resources": "2.5.0",
@@ -4184,7 +4178,6 @@
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
@@ -4458,7 +4451,6 @@
"integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.56.1",
"@typescript-eslint/types": "8.56.1",
@@ -5306,7 +5298,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -7860,7 +7851,6 @@
"integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -8493,7 +8483,6 @@
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
"license": "MIT",
"peer": true,
"dependencies": {
"accepts": "^2.0.0",
"body-parser": "^2.2.1",
@@ -9788,7 +9777,6 @@
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.2.tgz",
"integrity": "sha512-gJnaDHXKDayjt8ue0n8Gs0A007yKXj4Xzb8+cNjZeYsSzzwKc0Lr+OZgYwVfB0pHfUs17EPoLvrOsEaJ9mj+Tg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=16.9.0"
}
@@ -10068,7 +10056,6 @@
"resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.4.11.tgz",
"integrity": "sha512-93LQlzT7vvZ1XJcmOMwN4s+6W334QegendeHOMnEJBlhnpIzr8bws6/aOEHG8ZCuVD/vNeeea5m1msHIdAY6ig==",
"license": "MIT",
"peer": true,
"dependencies": {
"@alcalzone/ansi-tokenize": "^0.2.1",
"ansi-escapes": "^7.0.0",
@@ -13718,7 +13705,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -13729,7 +13715,6 @@
"integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"shell-quote": "^1.6.1",
"ws": "^7"
@@ -15689,7 +15674,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -15913,8 +15897,7 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD",
"peer": true
"license": "0BSD"
},
"node_modules/tsx": {
"version": "4.20.3",
@@ -15922,7 +15905,6 @@
"integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "~0.25.0",
"get-tsconfig": "^4.7.5"
@@ -16082,7 +16064,6 @@
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"devOptional": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -16291,7 +16272,6 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.5.0",
@@ -16405,7 +16385,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -16418,7 +16397,6 @@
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/chai": "^5.2.2",
"@vitest/expect": "3.2.4",
@@ -17063,7 +17041,6 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
@@ -17079,7 +17056,7 @@
},
"packages/a2a-server": {
"name": "@google/gemini-cli-a2a-server",
"version": "0.30.0-nightly.20260210.a2174751d",
"version": "0.30.0-test-github-release",
"dependencies": {
"@a2a-js/sdk": "^0.3.8",
"@google-cloud/storage": "^7.16.0",
@@ -17137,7 +17114,7 @@
},
"packages/cli": {
"name": "@google/gemini-cli",
"version": "0.30.0-nightly.20260210.a2174751d",
"version": "0.30.0-test-github-release",
"license": "Apache-2.0",
"dependencies": {
"@agentclientprotocol/sdk": "^0.12.0",
@@ -17220,7 +17197,7 @@
},
"packages/core": {
"name": "@google/gemini-cli-core",
"version": "0.30.0-nightly.20260210.a2174751d",
"version": "0.30.0-test-github-release",
"license": "Apache-2.0",
"dependencies": {
"@a2a-js/sdk": "^0.3.8",
@@ -17463,7 +17440,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -17486,7 +17462,7 @@
},
"packages/devtools": {
"name": "@google/gemini-cli-devtools",
"version": "0.30.0-nightly.20260210.a2174751d",
"version": "0.30.0-test-github-release",
"license": "Apache-2.0",
"dependencies": {
"ws": "^8.16.0"
@@ -17501,7 +17477,7 @@
},
"packages/sdk": {
"name": "@google/gemini-cli-sdk",
"version": "0.29.0-nightly.20260203.71f46f116",
"version": "0.30.0-test-github-release",
"license": "Apache-2.0",
"dependencies": {
"@google/gemini-cli-core": "file:../core",
@@ -17518,7 +17494,7 @@
},
"packages/test-utils": {
"name": "@google/gemini-cli-test-utils",
"version": "0.30.0-nightly.20260210.a2174751d",
"version": "0.30.0-test-github-release",
"license": "Apache-2.0",
"dependencies": {
"@google/gemini-cli-core": "file:../core",
@@ -17535,7 +17511,7 @@
},
"packages/vscode-ide-companion": {
"name": "gemini-cli-vscode-ide-companion",
"version": "0.30.0-nightly.20260210.a2174751d",
"version": "0.30.0-test-github-release",
"license": "LICENSE",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.23.0",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli",
"version": "0.30.0-nightly.20260210.a2174751d",
"version": "0.30.0-test-github-release",
"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.30.0-nightly.20260210.a2174751d"
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.30.0-test-github-release"
},
"scripts": {
"start": "cross-env NODE_ENV=development node scripts/start.js",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-a2a-server",
"version": "0.30.0-nightly.20260210.a2174751d",
"version": "0.30.0-test-github-release",
"description": "Gemini CLI A2A Server",
"repository": {
"type": "git",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli",
"version": "0.30.0-nightly.20260210.a2174751d",
"version": "0.30.0-test-github-release",
"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.30.0-nightly.20260210.a2174751d"
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.30.0-test-github-release"
},
"dependencies": {
"@agentclientprotocol/sdk": "^0.12.0",
@@ -13,34 +13,21 @@ import {
afterEach,
type Mock,
} from 'vitest';
import { format } from 'node:util';
import { coreEvents } from '@google/gemini-cli-core';
import { type Argv } from 'yargs';
import { handleLink, linkCommand } from './link.js';
import { ExtensionManager } from '../../config/extension-manager.js';
import { loadSettings, type LoadedSettings } from '../../config/settings.js';
import { getErrorMessage } from '../../utils/errors.js';
// Mock dependencies
const emitConsoleLog = vi.hoisted(() => vi.fn());
const debugLogger = vi.hoisted(() => ({
log: vi.fn((message, ...args) => {
emitConsoleLog('log', format(message, ...args));
}),
error: vi.fn((message, ...args) => {
emitConsoleLog('error', format(message, ...args));
}),
}));
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const actual =
await importOriginal<typeof import('@google/gemini-cli-core')>();
return {
...actual,
coreEvents: {
emitConsoleLog,
},
debugLogger,
};
const { mockCoreDebugLogger } = await import(
'../../test-utils/mockDebugLogger.js'
);
return mockCoreDebugLogger(
await importOriginal<typeof import('@google/gemini-cli-core')>(),
{ stripAnsi: true },
);
});
vi.mock('../../config/extension-manager.js');
@@ -95,7 +82,7 @@ describe('extensions link command', () => {
source: '/local/path/to/extension',
type: 'link',
});
expect(emitConsoleLog).toHaveBeenCalledWith(
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
'log',
'Extension "my-linked-extension" linked successfully and enabled.',
);
@@ -116,7 +103,7 @@ describe('extensions link command', () => {
await handleLink({ path: '/local/path/to/extension' });
expect(emitConsoleLog).toHaveBeenCalledWith(
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
'error',
'Link failed message',
);
@@ -5,33 +5,22 @@
*/
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
import { format } from 'node:util';
import { coreEvents } from '@google/gemini-cli-core';
import { handleList, listCommand } from './list.js';
import { ExtensionManager } from '../../config/extension-manager.js';
import { loadSettings, type LoadedSettings } from '../../config/settings.js';
import { getErrorMessage } from '../../utils/errors.js';
// Mock dependencies
const emitConsoleLog = vi.hoisted(() => vi.fn());
const debugLogger = vi.hoisted(() => ({
log: vi.fn((message, ...args) => {
emitConsoleLog('log', format(message, ...args));
}),
error: vi.fn((message, ...args) => {
emitConsoleLog('error', format(message, ...args));
}),
}));
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const actual =
await importOriginal<typeof import('@google/gemini-cli-core')>();
return {
...actual,
coreEvents: {
emitConsoleLog,
const { mockCoreDebugLogger } = await import(
'../../test-utils/mockDebugLogger.js'
);
return mockCoreDebugLogger(
await importOriginal<typeof import('@google/gemini-cli-core')>(),
{
stripAnsi: false,
},
debugLogger,
};
);
});
vi.mock('../../config/extension-manager.js');
@@ -71,7 +60,7 @@ describe('extensions list command', () => {
.mockResolvedValue([]);
await handleList();
expect(emitConsoleLog).toHaveBeenCalledWith(
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
'log',
'No extensions installed.',
);
@@ -85,7 +74,7 @@ describe('extensions list command', () => {
.mockResolvedValue([]);
await handleList({ outputFormat: 'json' });
expect(emitConsoleLog).toHaveBeenCalledWith('log', '[]');
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith('log', '[]');
mockCwd.mockRestore();
});
@@ -103,7 +92,7 @@ describe('extensions list command', () => {
);
await handleList();
expect(emitConsoleLog).toHaveBeenCalledWith(
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
'log',
'ext1@1.0.0\n\next2@2.0.0',
);
@@ -121,7 +110,7 @@ describe('extensions list command', () => {
.mockResolvedValue(extensions);
await handleList({ outputFormat: 'json' });
expect(emitConsoleLog).toHaveBeenCalledWith(
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
'log',
JSON.stringify(extensions, null, 2),
);
@@ -142,7 +131,7 @@ describe('extensions list command', () => {
await handleList();
expect(emitConsoleLog).toHaveBeenCalledWith(
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
'error',
'List failed message',
);
@@ -5,7 +5,6 @@
*/
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
import { format } from 'node:util';
import { handleDisable, disableCommand } from './disable.js';
import {
loadSettings,
@@ -14,12 +13,12 @@ import {
type LoadableSettingScope,
} from '../../config/settings.js';
const emitConsoleLog = vi.hoisted(() => vi.fn());
const debugLogger = vi.hoisted(() => ({
log: vi.fn((message, ...args) => {
emitConsoleLog('log', format(message, ...args));
}),
}));
const { emitConsoleLog, debugLogger } = await vi.hoisted(async () => {
const { createMockDebugLogger } = await import(
'../../test-utils/mockDebugLogger.js'
);
return createMockDebugLogger({ stripAnsi: true });
});
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const actual =
@@ -5,7 +5,6 @@
*/
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
import { format } from 'node:util';
import { handleEnable, enableCommand } from './enable.js';
import {
loadSettings,
@@ -13,12 +12,12 @@ import {
type LoadedSettings,
} from '../../config/settings.js';
const emitConsoleLog = vi.hoisted(() => vi.fn());
const debugLogger = vi.hoisted(() => ({
log: vi.fn((message, ...args) => {
emitConsoleLog('log', format(message, ...args));
}),
}));
const { emitConsoleLog, debugLogger } = await vi.hoisted(async () => {
const { createMockDebugLogger } = await import(
'../../test-utils/mockDebugLogger.js'
);
return createMockDebugLogger({ stripAnsi: true });
});
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const actual =
@@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { vi, describe, it, expect, beforeEach } from 'vitest';
const mockInstallSkill = vi.hoisted(() => vi.fn());
const mockRequestConsentNonInteractive = vi.hoisted(() => vi.fn());
@@ -19,11 +19,17 @@ vi.mock('../../config/extensions/consent.js', () => ({
skillsConsentString: mockSkillsConsentString,
}));
const { debugLogger, emitConsoleLog } = await vi.hoisted(async () => {
const { createMockDebugLogger } = await import(
'../../test-utils/mockDebugLogger.js'
);
return createMockDebugLogger({ stripAnsi: true });
});
vi.mock('@google/gemini-cli-core', () => ({
debugLogger: { log: vi.fn(), error: vi.fn() },
debugLogger,
}));
import { debugLogger } from '@google/gemini-cli-core';
import { handleInstall, installCommand } from './install.js';
describe('skill install command', () => {
@@ -63,10 +69,12 @@ describe('skill install command', () => {
expect.any(Function),
expect.any(Function),
);
expect(debugLogger.log).toHaveBeenCalledWith(
expect(emitConsoleLog).toHaveBeenCalledWith(
'log',
expect.stringContaining('Successfully installed skill: test-skill'),
);
expect(debugLogger.log).toHaveBeenCalledWith(
expect(emitConsoleLog).toHaveBeenCalledWith(
'log',
expect.stringContaining('location: /mock/user/skills/test-skill'),
);
expect(mockRequestConsentNonInteractive).toHaveBeenCalledWith(
@@ -86,10 +94,11 @@ describe('skill install command', () => {
});
expect(mockRequestConsentNonInteractive).not.toHaveBeenCalled();
expect(debugLogger.log).toHaveBeenCalledWith(
expect(emitConsoleLog).toHaveBeenCalledWith(
'log',
'You have consented to the following:',
);
expect(debugLogger.log).toHaveBeenCalledWith('Mock Consent String');
expect(emitConsoleLog).toHaveBeenCalledWith('log', 'Mock Consent String');
expect(mockInstallSkill).toHaveBeenCalled();
});
@@ -106,7 +115,8 @@ describe('skill install command', () => {
source: 'https://example.com/repo.git',
});
expect(debugLogger.error).toHaveBeenCalledWith(
expect(emitConsoleLog).toHaveBeenCalledWith(
'error',
'Skill installation cancelled by user.',
);
expect(process.exit).toHaveBeenCalledWith(1);
@@ -137,7 +147,7 @@ describe('skill install command', () => {
await handleInstall({ source: '/local/path' });
expect(debugLogger.error).toHaveBeenCalledWith('Install failed');
expect(emitConsoleLog).toHaveBeenCalledWith('error', 'Install failed');
expect(process.exit).toHaveBeenCalledWith(1);
});
});
@@ -15,8 +15,15 @@ vi.mock('../../utils/skillUtils.js', () => ({
linkSkill: mockLinkSkill,
}));
const { debugLogger } = await vi.hoisted(async () => {
const { createMockDebugLogger } = await import(
'../../test-utils/mockDebugLogger.js'
);
return createMockDebugLogger({ stripAnsi: false });
});
vi.mock('@google/gemini-cli-core', () => ({
debugLogger: { log: vi.fn(), error: vi.fn() },
debugLogger,
}));
vi.mock('../../config/extensions/consent.js', () => ({
@@ -24,8 +31,6 @@ vi.mock('../../config/extensions/consent.js', () => ({
skillsConsentString: mockSkillsConsentString,
}));
import { debugLogger } from '@google/gemini-cli-core';
describe('skills link command', () => {
beforeEach(() => {
vi.clearAllMocks();
+20 -30
View File
@@ -5,33 +5,23 @@
*/
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
import { format } from 'node:util';
import { coreEvents } from '@google/gemini-cli-core';
import { handleList, listCommand } from './list.js';
import { loadSettings, type LoadedSettings } from '../../config/settings.js';
import { loadCliConfig } from '../../config/config.js';
import type { Config } from '@google/gemini-cli-core';
import chalk from 'chalk';
const emitConsoleLog = vi.hoisted(() => vi.fn());
const debugLogger = vi.hoisted(() => ({
log: vi.fn((message, ...args) => {
emitConsoleLog('log', format(message, ...args));
}),
error: vi.fn((message, ...args) => {
emitConsoleLog('error', format(message, ...args));
}),
}));
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const actual =
await importOriginal<typeof import('@google/gemini-cli-core')>();
return {
...actual,
coreEvents: {
emitConsoleLog,
const { mockCoreDebugLogger } = await import(
'../../test-utils/mockDebugLogger.js'
);
return mockCoreDebugLogger(
await importOriginal<typeof import('@google/gemini-cli-core')>(),
{
stripAnsi: false,
},
debugLogger,
};
);
});
vi.mock('../../config/settings.js');
@@ -67,7 +57,7 @@ describe('skills list command', () => {
await handleList({});
expect(emitConsoleLog).toHaveBeenCalledWith(
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
'log',
'No skills discovered.',
);
@@ -98,23 +88,23 @@ describe('skills list command', () => {
await handleList({});
expect(emitConsoleLog).toHaveBeenCalledWith(
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
'log',
chalk.bold('Discovered Agent Skills:'),
);
expect(emitConsoleLog).toHaveBeenCalledWith(
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
'log',
expect.stringContaining('skill1'),
);
expect(emitConsoleLog).toHaveBeenCalledWith(
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
'log',
expect.stringContaining(chalk.green('[Enabled]')),
);
expect(emitConsoleLog).toHaveBeenCalledWith(
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
'log',
expect.stringContaining('skill2'),
);
expect(emitConsoleLog).toHaveBeenCalledWith(
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
'log',
expect.stringContaining(chalk.red('[Disabled]')),
);
@@ -146,11 +136,11 @@ describe('skills list command', () => {
// Default
await handleList({ all: false });
expect(emitConsoleLog).toHaveBeenCalledWith(
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
'log',
expect.stringContaining('regular'),
);
expect(emitConsoleLog).not.toHaveBeenCalledWith(
expect(coreEvents.emitConsoleLog).not.toHaveBeenCalledWith(
'log',
expect.stringContaining('builtin'),
);
@@ -159,15 +149,15 @@ describe('skills list command', () => {
// With all: true
await handleList({ all: true });
expect(emitConsoleLog).toHaveBeenCalledWith(
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
'log',
expect.stringContaining('regular'),
);
expect(emitConsoleLog).toHaveBeenCalledWith(
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
'log',
expect.stringContaining('builtin'),
);
expect(emitConsoleLog).toHaveBeenCalledWith(
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
'log',
expect.stringContaining(chalk.gray(' [Built-in]')),
);
@@ -12,11 +12,17 @@ vi.mock('../../utils/skillUtils.js', () => ({
uninstallSkill: mockUninstallSkill,
}));
const { debugLogger, emitConsoleLog } = await vi.hoisted(async () => {
const { createMockDebugLogger } = await import(
'../../test-utils/mockDebugLogger.js'
);
return createMockDebugLogger({ stripAnsi: true });
});
vi.mock('@google/gemini-cli-core', () => ({
debugLogger: { log: vi.fn(), error: vi.fn() },
debugLogger,
}));
import { debugLogger } from '@google/gemini-cli-core';
import { handleUninstall, uninstallCommand } from './uninstall.js';
describe('skill uninstall command', () => {
@@ -45,10 +51,12 @@ describe('skill uninstall command', () => {
});
expect(mockUninstallSkill).toHaveBeenCalledWith('test-skill', 'user');
expect(debugLogger.log).toHaveBeenCalledWith(
expect(emitConsoleLog).toHaveBeenCalledWith(
'log',
expect.stringContaining('Successfully uninstalled skill: test-skill'),
);
expect(debugLogger.log).toHaveBeenCalledWith(
expect(emitConsoleLog).toHaveBeenCalledWith(
'log',
expect.stringContaining('location: /mock/user/skills/test-skill'),
);
});
@@ -71,7 +79,8 @@ describe('skill uninstall command', () => {
await handleUninstall({ name: 'test-skill' });
expect(debugLogger.error).toHaveBeenCalledWith(
expect(emitConsoleLog).toHaveBeenCalledWith(
'error',
'Skill "test-skill" is not installed in the user scope.',
);
});
@@ -81,7 +90,7 @@ describe('skill uninstall command', () => {
await handleUninstall({ name: 'test-skill' });
expect(debugLogger.error).toHaveBeenCalledWith('Uninstall failed');
expect(emitConsoleLog).toHaveBeenCalledWith('error', 'Uninstall failed');
expect(process.exit).toHaveBeenCalledWith(1);
});
});
+1
View File
@@ -859,6 +859,7 @@ export async function loadCliConfig(
fakeResponses: argv.fakeResponses,
recordResponses: argv.recordResponses,
retryFetchErrors: settings.general?.retryFetchErrors,
maxAttempts: settings.general?.maxAttempts,
ptyInfo: ptyInfo?.name,
disableLLMCorrection: settings.tools?.disableLLMCorrection,
rawOutput: argv.rawOutput,
@@ -0,0 +1,188 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { ExtensionManager } from './extension-manager.js';
import { createTestMergedSettings } from './settings.js';
import { createExtension } from '../test-utils/createExtension.js';
import { EXTENSIONS_DIRECTORY_NAME } from './extensions/variables.js';
const mockHomedir = vi.hoisted(() => vi.fn(() => '/tmp/mock-home'));
vi.mock('os', async (importOriginal) => {
const mockedOs = await importOriginal<typeof os>();
return {
...mockedOs,
homedir: mockHomedir,
};
});
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const actual =
await importOriginal<typeof import('@google/gemini-cli-core')>();
return {
...actual,
homedir: mockHomedir,
};
});
describe('ExtensionManager', () => {
let tempHomeDir: string;
let tempWorkspaceDir: string;
let userExtensionsDir: string;
let extensionManager: ExtensionManager;
beforeEach(() => {
vi.clearAllMocks();
tempHomeDir = fs.mkdtempSync(
path.join(os.tmpdir(), 'gemini-cli-test-home-'),
);
tempWorkspaceDir = fs.mkdtempSync(
path.join(tempHomeDir, 'gemini-cli-test-workspace-'),
);
mockHomedir.mockReturnValue(tempHomeDir);
userExtensionsDir = path.join(tempHomeDir, EXTENSIONS_DIRECTORY_NAME);
fs.mkdirSync(userExtensionsDir, { recursive: true });
extensionManager = new ExtensionManager({
settings: createTestMergedSettings(),
workspaceDir: tempWorkspaceDir,
requestConsent: vi.fn().mockResolvedValue(true),
requestSetting: null,
});
});
afterEach(() => {
try {
fs.rmSync(tempHomeDir, { recursive: true, force: true });
} catch (_e) {
// Ignore
}
});
describe('loadExtensions parallel loading', () => {
it('should prevent concurrent loading and return the same promise', async () => {
createExtension({
extensionsDir: userExtensionsDir,
name: 'ext1',
version: '1.0.0',
});
createExtension({
extensionsDir: userExtensionsDir,
name: 'ext2',
version: '1.0.0',
});
// Call loadExtensions twice concurrently
const promise1 = extensionManager.loadExtensions();
const promise2 = extensionManager.loadExtensions();
// They should resolve to the exact same array
const [extensions1, extensions2] = await Promise.all([
promise1,
promise2,
]);
expect(extensions1).toBe(extensions2);
expect(extensions1).toHaveLength(2);
const names = extensions1.map((ext) => ext.name).sort();
expect(names).toEqual(['ext1', 'ext2']);
});
it('should throw an error if loadExtensions is called after it has already resolved', async () => {
createExtension({
extensionsDir: userExtensionsDir,
name: 'ext1',
version: '1.0.0',
});
await extensionManager.loadExtensions();
await expect(extensionManager.loadExtensions()).rejects.toThrow(
'Extensions already loaded, only load extensions once.',
);
});
it('should not throw if extension directory does not exist', async () => {
fs.rmSync(userExtensionsDir, { recursive: true, force: true });
const extensions = await extensionManager.loadExtensions();
expect(extensions).toEqual([]);
});
it('should throw if there are duplicate extension names', async () => {
// We manually create two extensions with different dirs but same name in config
const ext1Dir = path.join(userExtensionsDir, 'ext1-dir');
const ext2Dir = path.join(userExtensionsDir, 'ext2-dir');
fs.mkdirSync(ext1Dir, { recursive: true });
fs.mkdirSync(ext2Dir, { recursive: true });
const config = JSON.stringify({
name: 'duplicate-ext',
version: '1.0.0',
});
fs.writeFileSync(path.join(ext1Dir, 'gemini-extension.json'), config);
fs.writeFileSync(
path.join(ext1Dir, 'metadata.json'),
JSON.stringify({ type: 'local', source: ext1Dir }),
);
fs.writeFileSync(path.join(ext2Dir, 'gemini-extension.json'), config);
fs.writeFileSync(
path.join(ext2Dir, 'metadata.json'),
JSON.stringify({ type: 'local', source: ext2Dir }),
);
await expect(extensionManager.loadExtensions()).rejects.toThrow(
'Extension with name duplicate-ext already was loaded.',
);
});
it('should wait for loadExtensions to finish when loadExtension is called concurrently', async () => {
// Create an initial extension that loadExtensions will find
createExtension({
extensionsDir: userExtensionsDir,
name: 'ext1',
version: '1.0.0',
});
// Start the parallel load (it will read ext1)
const loadAllPromise = extensionManager.loadExtensions();
// Create a second extension dynamically in a DIFFERENT directory
// so that loadExtensions (which scans userExtensionsDir) doesn't find it.
const externalDir = fs.mkdtempSync(
path.join(os.tmpdir(), 'external-ext-'),
);
fs.writeFileSync(
path.join(externalDir, 'gemini-extension.json'),
JSON.stringify({ name: 'ext2', version: '1.0.0' }),
);
fs.writeFileSync(
path.join(externalDir, 'metadata.json'),
JSON.stringify({ type: 'local', source: externalDir }),
);
// Concurrently call loadExtension (simulating an install or update)
const loadSinglePromise = extensionManager.loadExtension(externalDir);
// Wait for both to complete
await Promise.all([loadAllPromise, loadSinglePromise]);
// Both extensions should now be present in the loadedExtensions array
const extensions = extensionManager.getExtensions();
expect(extensions).toHaveLength(2);
const names = extensions.map((ext) => ext.name).sort();
expect(names).toEqual(['ext1', 'ext2']);
fs.rmSync(externalDir, { recursive: true, force: true });
});
});
});
+89 -27
View File
@@ -102,6 +102,7 @@ export class ExtensionManager extends ExtensionLoader {
private telemetryConfig: Config;
private workspaceDir: string;
private loadedExtensions: GeminiCLIExtension[] | undefined;
private loadingPromise: Promise<GeminiCLIExtension[]> | null = null;
constructor(options: ExtensionManagerParams) {
super(options.eventEmitter);
@@ -519,31 +520,103 @@ Would you like to attempt to install via "git clone" instead?`,
throw new Error('Extensions already loaded, only load extensions once.');
}
if (this.settings.admin.extensions.enabled === false) {
this.loadedExtensions = [];
return this.loadedExtensions;
if (this.loadingPromise) {
return this.loadingPromise;
}
const extensionsDir = ExtensionStorage.getUserExtensionsDir();
this.loadedExtensions = [];
if (!fs.existsSync(extensionsDir)) {
return this.loadedExtensions;
}
for (const subdir of fs.readdirSync(extensionsDir)) {
const extensionDir = path.join(extensionsDir, subdir);
await this.loadExtension(extensionDir);
}
return this.loadedExtensions;
this.loadingPromise = (async () => {
try {
if (this.settings.admin.extensions.enabled === false) {
this.loadedExtensions = [];
return this.loadedExtensions;
}
const extensionsDir = ExtensionStorage.getUserExtensionsDir();
if (!fs.existsSync(extensionsDir)) {
this.loadedExtensions = [];
return this.loadedExtensions;
}
const subdirs = await fs.promises.readdir(extensionsDir);
const extensionPromises = subdirs.map((subdir) => {
const extensionDir = path.join(extensionsDir, subdir);
return this._buildExtension(extensionDir);
});
const builtExtensionsOrNull = await Promise.all(extensionPromises);
const builtExtensions = builtExtensionsOrNull.filter(
(ext): ext is GeminiCLIExtension => ext !== null,
);
const seenNames = new Set<string>();
for (const ext of builtExtensions) {
if (seenNames.has(ext.name)) {
throw new Error(
`Extension with name ${ext.name} already was loaded.`,
);
}
seenNames.add(ext.name);
}
this.loadedExtensions = builtExtensions;
await Promise.all(
this.loadedExtensions.map((ext) => this.maybeStartExtension(ext)),
);
return this.loadedExtensions;
} finally {
this.loadingPromise = null;
}
})();
return this.loadingPromise;
}
/**
* Adds `extension` to the list of extensions and starts it if appropriate.
*
* @internal visible for testing only
*/
private async loadExtension(
async loadExtension(
extensionDir: string,
): Promise<GeminiCLIExtension | null> {
if (this.loadingPromise) {
await this.loadingPromise;
}
this.loadedExtensions ??= [];
if (!fs.statSync(extensionDir).isDirectory()) {
const extension = await this._buildExtension(extensionDir);
if (!extension) {
return null;
}
if (
this.getExtensions().find(
(installed) => installed.name === extension.name,
)
) {
throw new Error(
`Extension with name ${extension.name} already was loaded.`,
);
}
this.loadedExtensions = [...this.loadedExtensions, extension];
await this.maybeStartExtension(extension);
return extension;
}
/**
* Builds an extension without side effects (does not mutate loadedExtensions or start it).
*/
private async _buildExtension(
extensionDir: string,
): Promise<GeminiCLIExtension | null> {
try {
const stats = await fs.promises.stat(extensionDir);
if (!stats.isDirectory()) {
return null;
}
} catch {
return null;
}
@@ -592,13 +665,6 @@ Would you like to attempt to install via "git clone" instead?`,
try {
let config = await this.loadExtensionConfig(effectiveExtensionPath);
if (
this.getExtensions().find((extension) => extension.name === config.name)
) {
throw new Error(
`Extension with name ${config.name} already was loaded.`,
);
}
const extensionId = getExtensionId(config, installMetadata);
@@ -768,7 +834,7 @@ Would you like to attempt to install via "git clone" instead?`,
);
}
const extension: GeminiCLIExtension = {
return {
name: config.name,
version: config.version,
path: effectiveExtensionPath,
@@ -788,10 +854,6 @@ Would you like to attempt to install via "git clone" instead?`,
agents: agentLoadResult.agents,
themes: config.themes,
};
this.loadedExtensions = [...this.loadedExtensions, extension];
await this.maybeStartExtension(extension);
return extension;
} catch (e) {
debugLogger.error(
`Warning: Skipping extension in ${effectiveExtensionPath}: ${getErrorMessage(
@@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="207" viewBox="0 0 920 207">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="207" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs">Installing extension &quot;test-ext&quot;. </text>
<text x="0" y="19" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs">This extension will run the following MCP servers: </text>
<text x="0" y="36" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs"> * server1 (local): npm start </text>
<text x="0" y="53" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs"> * server2 (remote): https://remote.com </text>
<text x="0" y="70" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs">This extension will append info to your gemini.md context using my-context.md </text>
<text x="0" y="87" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs">This extension will exclude the following core tools: tool1,tool2 </text>
<text x="0" y="121" fill="#cdcd00" textLength="891" lengthAdjust="spacingAndGlyphs">The extension you are about to install may have been created by a third-party developer and sourced</text>
<text x="0" y="138" fill="#cdcd00" textLength="882" lengthAdjust="spacingAndGlyphs">from a public repository. Google does not vet, endorse, or guarantee the functionality or security</text>
<text x="0" y="155" fill="#cdcd00" textLength="846" lengthAdjust="spacingAndGlyphs">of extensions. Please carefully inspect any extension and its source code before installing to</text>
<text x="0" y="172" fill="#cdcd00" textLength="630" lengthAdjust="spacingAndGlyphs">understand the permissions it requires and the actions it may perform.</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="139" viewBox="0 0 920 139">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="139" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs">Installing extension &quot;test-ext&quot;. </text>
<text x="0" y="19" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs">⚠️ This extension contains Hooks which can automatically execute commands. </text>
<text x="0" y="53" fill="#cdcd00" textLength="891" lengthAdjust="spacingAndGlyphs">The extension you are about to install may have been created by a third-party developer and sourced</text>
<text x="0" y="70" fill="#cdcd00" textLength="882" lengthAdjust="spacingAndGlyphs">from a public repository. Google does not vet, endorse, or guarantee the functionality or security</text>
<text x="0" y="87" fill="#cdcd00" textLength="846" lengthAdjust="spacingAndGlyphs">of extensions. Please carefully inspect any extension and its source code before installing to</text>
<text x="0" y="104" fill="#cdcd00" textLength="630" lengthAdjust="spacingAndGlyphs">understand the permissions it requires and the actions it may perform.</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,28 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="479" viewBox="0 0 920 479">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="479" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs">Installing extension &quot;test-ext&quot;. </text>
<text x="0" y="19" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs">This extension will run the following MCP servers: </text>
<text x="0" y="36" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs"> * server1 (local): npm start </text>
<text x="0" y="53" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs"> * server2 (remote): https://remote.com </text>
<text x="0" y="70" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs">This extension will append info to your gemini.md context using my-context.md </text>
<text x="0" y="87" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs">This extension will exclude the following core tools: tool1,tool2 </text>
<text x="0" y="121" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs">Agent Skills: </text>
<text x="0" y="155" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs">This extension will install the following agent skills: </text>
<text x="0" y="189" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs"> * skill1: desc1 </text>
<text x="0" y="206" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs"> (Source: /mock/temp/dir/skill1/SKILL.md) (2 items in directory) </text>
<text x="0" y="240" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs"> * skill2: desc2 </text>
<text x="0" y="257" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs"> (Source: /mock/temp/dir/skill2/SKILL.md) (1 items in directory) </text>
<text x="0" y="308" fill="#cdcd00" textLength="891" lengthAdjust="spacingAndGlyphs">The extension you are about to install may have been created by a third-party developer and sourced</text>
<text x="0" y="325" fill="#cdcd00" textLength="882" lengthAdjust="spacingAndGlyphs">from a public repository. Google does not vet, endorse, or guarantee the functionality or security</text>
<text x="0" y="342" fill="#cdcd00" textLength="846" lengthAdjust="spacingAndGlyphs">of extensions. Please carefully inspect any extension and its source code before installing to</text>
<text x="0" y="359" fill="#cdcd00" textLength="630" lengthAdjust="spacingAndGlyphs">understand the permissions it requires and the actions it may perform.</text>
<text x="0" y="393" fill="#cdcd00" textLength="882" lengthAdjust="spacingAndGlyphs">Agent skills inject specialized instructions and domain-specific knowledge into the agent&apos;s system</text>
<text x="0" y="410" fill="#cdcd00" textLength="891" lengthAdjust="spacingAndGlyphs">prompt. This can change how the agent interprets your requests and interacts with your environment.</text>
<text x="0" y="427" fill="#cdcd00" textLength="864" lengthAdjust="spacingAndGlyphs">Review the skill definitions at the location(s) provided below to ensure they meet your security</text>
<text x="0" y="444" fill="#cdcd00" textLength="90" lengthAdjust="spacingAndGlyphs">standards.</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="343" viewBox="0 0 920 343">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="343" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs">Installing extension &quot;test-ext&quot;. </text>
<text x="0" y="36" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs">Agent Skills: </text>
<text x="0" y="70" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs">This extension will install the following agent skills: </text>
<text x="0" y="104" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs"> * locked-skill: A skill in a locked dir </text>
<text x="0" y="121" fill="#ffffff" textLength="405" lengthAdjust="spacingAndGlyphs"> (Source: /mock/temp/dir/locked/SKILL.md) </text>
<text x="405" y="121" fill="#cd0000" textLength="342" lengthAdjust="spacingAndGlyphs">⚠️ (Could not count items in directory)</text>
<text x="0" y="172" fill="#cdcd00" textLength="891" lengthAdjust="spacingAndGlyphs">The extension you are about to install may have been created by a third-party developer and sourced</text>
<text x="0" y="189" fill="#cdcd00" textLength="882" lengthAdjust="spacingAndGlyphs">from a public repository. Google does not vet, endorse, or guarantee the functionality or security</text>
<text x="0" y="206" fill="#cdcd00" textLength="846" lengthAdjust="spacingAndGlyphs">of extensions. Please carefully inspect any extension and its source code before installing to</text>
<text x="0" y="223" fill="#cdcd00" textLength="630" lengthAdjust="spacingAndGlyphs">understand the permissions it requires and the actions it may perform.</text>
<text x="0" y="257" fill="#cdcd00" textLength="882" lengthAdjust="spacingAndGlyphs">Agent skills inject specialized instructions and domain-specific knowledge into the agent&apos;s system</text>
<text x="0" y="274" fill="#cdcd00" textLength="891" lengthAdjust="spacingAndGlyphs">prompt. This can change how the agent interprets your requests and interacts with your environment.</text>
<text x="0" y="291" fill="#cdcd00" textLength="864" lengthAdjust="spacingAndGlyphs">Review the skill definitions at the location(s) provided below to ensure they meet your security</text>
<text x="0" y="308" fill="#cdcd00" textLength="90" lengthAdjust="spacingAndGlyphs">standards.</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="241" viewBox="0 0 920 241">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="241" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs">Installing agent skill(s) from &quot;https://example.com/repo.git&quot;. </text>
<text x="0" y="36" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs">The following agent skill(s) will be installing: </text>
<text x="0" y="70" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs"> * skill1: desc1 </text>
<text x="0" y="87" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs"> (Source: /mock/temp/dir/skill1/SKILL.md) (1 items in directory) </text>
<text x="0" y="121" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs">Install Destination: /mock/target/dir </text>
<text x="0" y="155" fill="#cdcd00" textLength="882" lengthAdjust="spacingAndGlyphs">Agent skills inject specialized instructions and domain-specific knowledge into the agent&apos;s system</text>
<text x="0" y="172" fill="#cdcd00" textLength="891" lengthAdjust="spacingAndGlyphs">prompt. This can change how the agent interprets your requests and interacts with your environment.</text>
<text x="0" y="189" fill="#cdcd00" textLength="864" lengthAdjust="spacingAndGlyphs">Review the skill definitions at the location(s) provided below to ensure they meet your security</text>
<text x="0" y="206" fill="#cdcd00" textLength="90" lengthAdjust="spacingAndGlyphs">standards.</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -0,0 +1,93 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`consent > maybeRequestConsentOrFail > consent string generation > should generate a consent string with all fields 1`] = `
"Installing extension "test-ext".
This extension will run the following MCP servers:
* server1 (local): npm start
* server2 (remote): https://remote.com
This extension will append info to your gemini.md context using my-context.md
This extension will exclude the following core tools: tool1,tool2
The extension you are about to install may have been created by a third-party developer and sourced
from a public repository. Google does not vet, endorse, or guarantee the functionality or security
of extensions. Please carefully inspect any extension and its source code before installing to
understand the permissions it requires and the actions it may perform."
`;
exports[`consent > maybeRequestConsentOrFail > consent string generation > should include warning when hooks are present 1`] = `
"Installing extension "test-ext".
⚠️ This extension contains Hooks which can automatically execute commands.
The extension you are about to install may have been created by a third-party developer and sourced
from a public repository. Google does not vet, endorse, or guarantee the functionality or security
of extensions. Please carefully inspect any extension and its source code before installing to
understand the permissions it requires and the actions it may perform."
`;
exports[`consent > maybeRequestConsentOrFail > consent string generation > should request consent if skills change 1`] = `
"Installing extension "test-ext".
This extension will run the following MCP servers:
* server1 (local): npm start
* server2 (remote): https://remote.com
This extension will append info to your gemini.md context using my-context.md
This extension will exclude the following core tools: tool1,tool2
Agent Skills:
This extension will install the following agent skills:
* skill1: desc1
(Source: /mock/temp/dir/skill1/SKILL.md) (2 items in directory)
* skill2: desc2
(Source: /mock/temp/dir/skill2/SKILL.md) (1 items in directory)
The extension you are about to install may have been created by a third-party developer and sourced
from a public repository. Google does not vet, endorse, or guarantee the functionality or security
of extensions. Please carefully inspect any extension and its source code before installing to
understand the permissions it requires and the actions it may perform.
Agent skills inject specialized instructions and domain-specific knowledge into the agent's system
prompt. This can change how the agent interprets your requests and interacts with your environment.
Review the skill definitions at the location(s) provided below to ensure they meet your security
standards."
`;
exports[`consent > maybeRequestConsentOrFail > consent string generation > should show a warning if the skill directory cannot be read 1`] = `
"Installing extension "test-ext".
Agent Skills:
This extension will install the following agent skills:
* locked-skill: A skill in a locked dir
(Source: /mock/temp/dir/locked/SKILL.md) ⚠️ (Could not count items in directory)
The extension you are about to install may have been created by a third-party developer and sourced
from a public repository. Google does not vet, endorse, or guarantee the functionality or security
of extensions. Please carefully inspect any extension and its source code before installing to
understand the permissions it requires and the actions it may perform.
Agent skills inject specialized instructions and domain-specific knowledge into the agent's system
prompt. This can change how the agent interprets your requests and interacts with your environment.
Review the skill definitions at the location(s) provided below to ensure they meet your security
standards."
`;
exports[`consent > skillsConsentString > should generate a consent string for skills 1`] = `
"Installing agent skill(s) from "https://example.com/repo.git".
The following agent skill(s) will be installing:
* skill1: desc1
(Source: /mock/temp/dir/skill1/SKILL.md) (1 items in directory)
Install Destination: /mock/target/dir
Agent skills inject specialized instructions and domain-specific knowledge into the agent's system
prompt. This can change how the agent interprets your requests and interacts with your environment.
Review the skill definitions at the location(s) provided below to ensure they meet your security
standards."
`;
@@ -4,17 +4,17 @@
* SPDX-License-Identifier: Apache-2.0
*/
import React from 'react';
import { Text } from 'ink';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import chalk from 'chalk';
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import * as os from 'node:os';
import { render, cleanup } from '../../test-utils/render.js';
import {
requestConsentNonInteractive,
requestConsentInteractive,
maybeRequestConsentOrFail,
INSTALL_WARNING_MESSAGE,
SKILLS_WARNING_MESSAGE,
} from './consent.js';
import type { ConfirmationRequest } from '../../ui/types.js';
import type { ExtensionConfig } from '../extension.js';
@@ -58,6 +58,21 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
};
});
async function expectConsentSnapshot(consentString: string) {
const renderResult = render(React.createElement(Text, null, consentString));
await renderResult.waitUntilReady();
await expect(renderResult).toMatchSvgSnapshot();
}
/**
* Normalizes a consent string for snapshot testing by:
* 1. Replacing the dynamic temp directory path with a static placeholder.
* 2. Converting Windows backslashes to forward slashes for platform-agnosticism.
*/
function normalizePathsForSnapshot(str: string, tempDir: string): string {
return str.replaceAll(tempDir, '/mock/temp/dir').replaceAll('\\', '/');
}
describe('consent', () => {
let tempDir: string;
@@ -75,6 +90,7 @@ describe('consent', () => {
if (tempDir) {
await fs.rm(tempDir, { recursive: true, force: true });
}
cleanup();
});
describe('requestConsentNonInteractive', () => {
@@ -189,18 +205,9 @@ describe('consent', () => {
undefined,
);
const expectedConsentString = [
'Installing extension "test-ext".',
'This extension will run the following MCP servers:',
' * server1 (local): npm start',
' * server2 (remote): https://remote.com',
'This extension will append info to your gemini.md context using my-context.md',
'This extension will exclude the following core tools: tool1,tool2',
'',
INSTALL_WARNING_MESSAGE,
].join('\n');
expect(requestConsent).toHaveBeenCalledWith(expectedConsentString);
expect(requestConsent).toHaveBeenCalledTimes(1);
const consentString = requestConsent.mock.calls[0][0] as string;
await expectConsentSnapshot(consentString);
});
it('should request consent if mcpServers change', async () => {
@@ -263,11 +270,9 @@ describe('consent', () => {
undefined,
);
expect(requestConsent).toHaveBeenCalledWith(
expect.stringContaining(
'⚠️ This extension contains Hooks which can automatically execute commands.',
),
);
expect(requestConsent).toHaveBeenCalledTimes(1);
const consentString = requestConsent.mock.calls[0][0] as string;
await expectConsentSnapshot(consentString);
});
it('should request consent if hooks status changes', async () => {
@@ -323,29 +328,10 @@ describe('consent', () => {
[skill1, skill2],
);
const expectedConsentString = [
'Installing extension "test-ext".',
'This extension will run the following MCP servers:',
' * server1 (local): npm start',
' * server2 (remote): https://remote.com',
'This extension will append info to your gemini.md context using my-context.md',
'This extension will exclude the following core tools: tool1,tool2',
'',
chalk.bold('Agent Skills:'),
'\nThis extension will install the following agent skills:\n',
` * ${chalk.bold('skill1')}: desc1`,
chalk.dim(` (Source: ${skill1.location}) (2 items in directory)`),
'',
` * ${chalk.bold('skill2')}: desc2`,
chalk.dim(` (Source: ${skill2.location}) (1 items in directory)`),
'',
'',
INSTALL_WARNING_MESSAGE,
'',
SKILLS_WARNING_MESSAGE,
].join('\n');
expect(requestConsent).toHaveBeenCalledWith(expectedConsentString);
expect(requestConsent).toHaveBeenCalledTimes(1);
let consentString = requestConsent.mock.calls[0][0] as string;
consentString = normalizePathsForSnapshot(consentString, tempDir);
await expectConsentSnapshot(consentString);
});
it('should show a warning if the skill directory cannot be read', async () => {
@@ -377,11 +363,10 @@ describe('consent', () => {
[skill],
);
expect(requestConsent).toHaveBeenCalledWith(
expect.stringContaining(
` (Source: ${skill.location}) ${chalk.red('⚠️ (Could not count items in directory)')}`,
),
);
expect(requestConsent).toHaveBeenCalledTimes(1);
let consentString = requestConsent.mock.calls[0][0] as string;
consentString = normalizePathsForSnapshot(consentString, tempDir);
await expectConsentSnapshot(consentString);
});
});
});
@@ -400,21 +385,14 @@ describe('consent', () => {
};
const { skillsConsentString } = await import('./consent.js');
const consentString = await skillsConsentString(
let consentString = await skillsConsentString(
[skill1],
'https://example.com/repo.git',
'/mock/target/dir',
);
expect(consentString).toContain(
'Installing agent skill(s) from "https://example.com/repo.git".',
);
expect(consentString).toContain('Install Destination: /mock/target/dir');
expect(consentString).toContain('\n' + SKILLS_WARNING_MESSAGE);
expect(consentString).toContain(` * ${chalk.bold('skill1')}: desc1`);
expect(consentString).toContain(
chalk.dim(`(Source: ${skill1.location}) (1 items in directory)`),
);
consentString = normalizePathsForSnapshot(consentString, tempDir);
await expectConsentSnapshot(consentString);
});
});
});
+57 -28
View File
@@ -8,7 +8,11 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as os from 'node:os';
import { resolveWorkspacePolicyState } from './policy.js';
import {
resolveWorkspacePolicyState,
autoAcceptWorkspacePolicies,
setAutoAcceptWorkspacePolicies,
} from './policy.js';
import { writeToStderr } from '@google/gemini-cli-core';
// Mock debugLogger to avoid noise in test output
@@ -68,24 +72,18 @@ describe('resolveWorkspacePolicyState', () => {
fs.mkdirSync(policiesDir, { recursive: true });
fs.writeFileSync(path.join(policiesDir, 'policy.toml'), 'rules = []');
// First call to establish integrity (interactive accept)
// First call to establish integrity (interactive auto-accept)
const firstResult = await resolveWorkspacePolicyState({
cwd: workspaceDir,
trustedFolder: true,
interactive: true,
});
expect(firstResult.policyUpdateConfirmationRequest).toBeDefined();
// Establish integrity manually as if accepted
const { PolicyIntegrityManager } = await import('@google/gemini-cli-core');
const integrityManager = new PolicyIntegrityManager();
await integrityManager.acceptIntegrity(
'workspace',
workspaceDir,
firstResult.policyUpdateConfirmationRequest!.newHash,
);
expect(firstResult.workspacePoliciesDir).toBe(policiesDir);
expect(firstResult.policyUpdateConfirmationRequest).toBeUndefined();
expect(writeToStderr).not.toHaveBeenCalled();
// Second call should match
const result = await resolveWorkspacePolicyState({
cwd: workspaceDir,
trustedFolder: true,
@@ -107,26 +105,33 @@ describe('resolveWorkspacePolicyState', () => {
expect(result.policyUpdateConfirmationRequest).toBeUndefined();
});
it('should return confirmation request if changed in interactive mode', async () => {
fs.mkdirSync(policiesDir, { recursive: true });
fs.writeFileSync(path.join(policiesDir, 'policy.toml'), 'rules = []');
it('should return confirmation request if changed in interactive mode when AUTO_ACCEPT is false', async () => {
const originalValue = autoAcceptWorkspacePolicies;
setAutoAcceptWorkspacePolicies(false);
const result = await resolveWorkspacePolicyState({
cwd: workspaceDir,
trustedFolder: true,
interactive: true,
});
try {
fs.mkdirSync(policiesDir, { recursive: true });
fs.writeFileSync(path.join(policiesDir, 'policy.toml'), 'rules = []');
expect(result.workspacePoliciesDir).toBeUndefined();
expect(result.policyUpdateConfirmationRequest).toEqual({
scope: 'workspace',
identifier: workspaceDir,
policyDir: policiesDir,
newHash: expect.any(String),
});
const result = await resolveWorkspacePolicyState({
cwd: workspaceDir,
trustedFolder: true,
interactive: true,
});
expect(result.workspacePoliciesDir).toBeUndefined();
expect(result.policyUpdateConfirmationRequest).toEqual({
scope: 'workspace',
identifier: workspaceDir,
policyDir: policiesDir,
newHash: expect.any(String),
});
} finally {
setAutoAcceptWorkspacePolicies(originalValue);
}
});
it('should warn and auto-accept if changed in non-interactive mode', async () => {
it('should warn and auto-accept if changed in non-interactive mode when AUTO_ACCEPT is true', async () => {
fs.mkdirSync(policiesDir, { recursive: true });
fs.writeFileSync(path.join(policiesDir, 'policy.toml'), 'rules = []');
@@ -143,6 +148,30 @@ describe('resolveWorkspacePolicyState', () => {
);
});
it('should warn and auto-accept if changed in non-interactive mode when AUTO_ACCEPT is false', async () => {
const originalValue = autoAcceptWorkspacePolicies;
setAutoAcceptWorkspacePolicies(false);
try {
fs.mkdirSync(policiesDir, { recursive: true });
fs.writeFileSync(path.join(policiesDir, 'policy.toml'), 'rules = []');
const result = await resolveWorkspacePolicyState({
cwd: workspaceDir,
trustedFolder: true,
interactive: false,
});
expect(result.workspacePoliciesDir).toBe(policiesDir);
expect(result.policyUpdateConfirmationRequest).toBeUndefined();
expect(writeToStderr).toHaveBeenCalledWith(
expect.stringContaining('Automatically accepting and loading'),
);
} finally {
setAutoAcceptWorkspacePolicies(originalValue);
}
});
it('should not return workspace policies if cwd is the home directory', async () => {
const policiesDir = path.join(tempDir, '.gemini', 'policies');
fs.mkdirSync(policiesDir, { recursive: true });
+28 -7
View File
@@ -17,9 +17,24 @@ import {
Storage,
type PolicyUpdateConfirmationRequest,
writeToStderr,
debugLogger,
} from '@google/gemini-cli-core';
import { type Settings } from './settings.js';
/**
* Temporary flag to automatically accept workspace policies to reduce friction.
* Exported as 'let' to allow monkey patching in tests via the setter.
*/
export let autoAcceptWorkspacePolicies = true;
/**
* Sets the autoAcceptWorkspacePolicies flag.
* Used primarily for testing purposes.
*/
export function setAutoAcceptWorkspacePolicies(value: boolean) {
autoAcceptWorkspacePolicies = value;
}
export async function createPolicyEngineConfig(
settings: Settings,
approvalMode: ApprovalMode,
@@ -91,8 +106,8 @@ export async function resolveWorkspacePolicyState(options: {
) {
// No workspace policies found
workspacePoliciesDir = undefined;
} else if (interactive) {
// Policies changed or are new, and we are in interactive mode
} else if (interactive && !autoAcceptWorkspacePolicies) {
// Policies changed or are new, and we are in interactive mode and auto-accept is disabled
policyUpdateConfirmationRequest = {
scope: 'workspace',
identifier: cwd,
@@ -100,17 +115,23 @@ export async function resolveWorkspacePolicyState(options: {
newHash: integrityResult.hash,
};
} else {
// Non-interactive mode: warn and automatically accept/load
// Non-interactive mode or auto-accept is enabled: automatically accept/load
await integrityManager.acceptIntegrity(
'workspace',
cwd,
integrityResult.hash,
);
workspacePoliciesDir = potentialWorkspacePoliciesDir;
// debugLogger.warn here doesn't show up in the terminal. It is showing up only in debug mode on the debug console
writeToStderr(
'WARNING: Workspace policies changed or are new. Automatically accepting and loading them in non-interactive mode.\n',
);
if (!interactive) {
writeToStderr(
'WARNING: Workspace policies changed or are new. Automatically accepting and loading them.\n',
);
} else {
debugLogger.warn(
'Workspace policies changed or are new. Automatically accepting and loading them.',
);
}
}
}
+1 -1
View File
@@ -844,7 +844,7 @@ const SETTINGS_SCHEMA = {
requiresRestart: false,
default: undefined as string | undefined,
description: 'The Gemini model to use for conversations.',
showInDialog: false,
showInDialog: true,
},
maxSessionTurns: {
type: 'number',
@@ -10,6 +10,7 @@ import { loadCliConfig, type CliArgs } from './config.js';
import { createTestMergedSettings } from './settings.js';
import * as ServerConfig from '@google/gemini-cli-core';
import { isWorkspaceTrusted } from './trustedFolders.js';
import * as Policy from './policy.js';
// Mock dependencies
vi.mock('./trustedFolders.js', () => ({
@@ -164,7 +165,7 @@ describe('Workspace-Level Policy CLI Integration', () => {
);
});
it('should set policyUpdateConfirmationRequest if integrity MISMATCH in interactive mode', async () => {
it('should automatically accept and load workspacePoliciesDir if integrity MISMATCH in interactive mode when AUTO_ACCEPT is true', async () => {
vi.mocked(isWorkspaceTrusted).mockReturnValue({
isTrusted: true,
source: 'file',
@@ -186,24 +187,23 @@ describe('Workspace-Level Policy CLI Integration', () => {
cwd: MOCK_CWD,
});
expect(config.getPolicyUpdateConfirmationRequest()).toEqual({
scope: 'workspace',
identifier: MOCK_CWD,
policyDir: expect.stringContaining(path.join('.gemini', 'policies')),
newHash: 'new-hash',
});
// In interactive mode without accept flag, it waits for user confirmation (handled by UI),
// so it currently DOES NOT pass the directory to createPolicyEngineConfig yet.
// The UI will handle the confirmation and reload/update.
expect(config.getPolicyUpdateConfirmationRequest()).toBeUndefined();
expect(mockAcceptIntegrity).toHaveBeenCalledWith(
'workspace',
MOCK_CWD,
'new-hash',
);
expect(ServerConfig.createPolicyEngineConfig).toHaveBeenCalledWith(
expect.objectContaining({
workspacePoliciesDir: undefined,
workspacePoliciesDir: expect.stringContaining(
path.join('.gemini', 'policies'),
),
}),
expect.anything(),
);
});
it('should set policyUpdateConfirmationRequest if integrity is NEW with files (first time seen) in interactive mode', async () => {
it('should automatically accept and load workspacePoliciesDir if integrity is NEW in interactive mode when AUTO_ACCEPT is true', async () => {
vi.mocked(isWorkspaceTrusted).mockReturnValue({
isTrusted: true,
source: 'file',
@@ -222,18 +222,65 @@ describe('Workspace-Level Policy CLI Integration', () => {
cwd: MOCK_CWD,
});
expect(config.getPolicyUpdateConfirmationRequest()).toEqual({
scope: 'workspace',
identifier: MOCK_CWD,
policyDir: expect.stringContaining(path.join('.gemini', 'policies')),
newHash: 'new-hash',
});
expect(config.getPolicyUpdateConfirmationRequest()).toBeUndefined();
expect(mockAcceptIntegrity).toHaveBeenCalledWith(
'workspace',
MOCK_CWD,
'new-hash',
);
expect(ServerConfig.createPolicyEngineConfig).toHaveBeenCalledWith(
expect.objectContaining({
workspacePoliciesDir: undefined,
workspacePoliciesDir: expect.stringContaining(
path.join('.gemini', 'policies'),
),
}),
expect.anything(),
);
});
it('should set policyUpdateConfirmationRequest if integrity MISMATCH in interactive mode when AUTO_ACCEPT is false', async () => {
// Monkey patch autoAcceptWorkspacePolicies using setter
const originalValue = Policy.autoAcceptWorkspacePolicies;
Policy.setAutoAcceptWorkspacePolicies(false);
try {
vi.mocked(isWorkspaceTrusted).mockReturnValue({
isTrusted: true,
source: 'file',
});
mockCheckIntegrity.mockResolvedValue({
status: 'mismatch',
hash: 'new-hash',
fileCount: 1,
});
vi.mocked(ServerConfig.isHeadlessMode).mockReturnValue(false); // Interactive
const settings = createTestMergedSettings();
const argv = {
query: 'test',
promptInteractive: 'test',
} as unknown as CliArgs;
const config = await loadCliConfig(settings, 'test-session', argv, {
cwd: MOCK_CWD,
});
expect(config.getPolicyUpdateConfirmationRequest()).toEqual({
scope: 'workspace',
identifier: MOCK_CWD,
policyDir: expect.stringContaining(path.join('.gemini', 'policies')),
newHash: 'new-hash',
});
expect(ServerConfig.createPolicyEngineConfig).toHaveBeenCalledWith(
expect.objectContaining({
workspacePoliciesDir: undefined,
}),
expect.anything(),
);
} finally {
// Restore for other tests
Policy.setAutoAcceptWorkspacePolicies(originalValue);
}
});
});
+77 -12
View File
@@ -6,20 +6,78 @@
/// <reference types="vitest/globals" />
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import type { Assertion } from 'vitest';
import { expect } from 'vitest';
import { expect, type Assertion } from 'vitest';
import path from 'node:path';
import stripAnsi from 'strip-ansi';
import type { TextBuffer } from '../ui/components/shared/text-buffer.js';
// RegExp to detect invalid characters: backspace, and ANSI escape codes
// eslint-disable-next-line no-control-regex
const invalidCharsRegex = /[\b\x1b]/;
const callCountByTest = new Map<string, number>();
export async function toMatchSvgSnapshot(
this: Assertion,
renderInstance: {
lastFrameRaw?: (options?: { allowEmpty?: boolean }) => string;
lastFrame?: (options?: { allowEmpty?: boolean }) => string;
generateSvg: () => string;
},
options?: { allowEmpty?: boolean; name?: string },
) {
const currentTestName = expect.getState().currentTestName;
if (!currentTestName) {
throw new Error('toMatchSvgSnapshot must be called within a test');
}
const testPath = expect.getState().testPath;
if (!testPath) {
throw new Error('toMatchSvgSnapshot requires testPath');
}
let textContent: string;
if (renderInstance.lastFrameRaw) {
textContent = renderInstance.lastFrameRaw({
allowEmpty: options?.allowEmpty,
});
} else if (renderInstance.lastFrame) {
textContent = renderInstance.lastFrame({ allowEmpty: options?.allowEmpty });
} else {
throw new Error(
'toMatchSvgSnapshot requires a renderInstance with either lastFrameRaw or lastFrame',
);
}
const svgContent = renderInstance.generateSvg();
const sanitize = (name: string) =>
name.replace(/[^a-zA-Z0-9_-]/g, '-').replace(/-+/g, '-');
const testId = testPath + ':' + currentTestName;
let count = callCountByTest.get(testId) ?? 0;
count++;
callCountByTest.set(testId, count);
const snapshotName =
options?.name ??
(count > 1 ? `${currentTestName}-${count}` : currentTestName);
const svgFileName =
sanitize(path.basename(testPath).replace(/\.test\.tsx?$/, '')) +
'-' +
sanitize(snapshotName) +
'.snap.svg';
const svgDir = path.join(path.dirname(testPath), '__snapshots__');
const svgFilePath = path.join(svgDir, svgFileName);
// Assert the text matches standard snapshot, stripping ANSI for stability
expect(stripAnsi(textContent)).toMatchSnapshot();
// Assert the SVG matches the file snapshot
await expect(svgContent).toMatchFileSnapshot(svgFilePath);
return { pass: true, message: () => '' };
}
function toHaveOnlyValidCharacters(this: Assertion, buffer: TextBuffer) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-assignment
const { isNot } = this as any;
@@ -53,15 +111,22 @@ function toHaveOnlyValidCharacters(this: Assertion, buffer: TextBuffer) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
expect.extend({
toHaveOnlyValidCharacters,
toMatchSvgSnapshot,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any);
// Extend Vitest's `expect` interface with the custom matcher's type definition.
declare module 'vitest' {
interface Assertion<T> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-empty-object-type
interface Assertion<T = any> extends CustomMatchers<T> {}
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
interface AsymmetricMatchersContaining extends CustomMatchers {}
interface CustomMatchers<T = unknown> {
toHaveOnlyValidCharacters(): T;
}
interface AsymmetricMatchersContaining {
toHaveOnlyValidCharacters(): void;
toMatchSvgSnapshot(options?: {
allowEmpty?: boolean;
name?: string;
}): Promise<void>;
}
}
@@ -0,0 +1,76 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { vi } from 'vitest';
import stripAnsi from 'strip-ansi';
import { format } from 'node:util';
export function createMockDebugLogger(options: { stripAnsi?: boolean } = {}) {
const emitConsoleLog = vi.fn();
const debugLogger = {
log: vi.fn((message: unknown, ...args: unknown[]) => {
let formatted =
typeof message === 'string' ? format(message, ...args) : message;
if (options.stripAnsi && typeof formatted === 'string') {
formatted = stripAnsi(formatted);
}
emitConsoleLog('log', formatted);
}),
error: vi.fn((message: unknown, ...args: unknown[]) => {
let formatted =
typeof message === 'string' ? format(message, ...args) : message;
if (options.stripAnsi && typeof formatted === 'string') {
formatted = stripAnsi(formatted);
}
emitConsoleLog('error', formatted);
}),
warn: vi.fn((message: unknown, ...args: unknown[]) => {
let formatted =
typeof message === 'string' ? format(message, ...args) : message;
if (options.stripAnsi && typeof formatted === 'string') {
formatted = stripAnsi(formatted);
}
emitConsoleLog('warn', formatted);
}),
debug: vi.fn(),
info: vi.fn(),
};
return { emitConsoleLog, debugLogger };
}
/**
* A helper specifically designed for `vi.mock('@google/gemini-cli-core', ...)` to easily
* mock both `debugLogger` and `coreEvents.emitConsoleLog`.
*
* Example:
* ```typescript
* vi.mock('@google/gemini-cli-core', async (importOriginal) => {
* const { mockCoreDebugLogger } = await import('../../test-utils/mockDebugLogger.js');
* return mockCoreDebugLogger(
* await importOriginal<typeof import('@google/gemini-cli-core')>(),
* { stripAnsi: true }
* );
* });
* ```
*/
export function mockCoreDebugLogger<T extends Record<string, unknown>>(
actual: T,
options?: { stripAnsi?: boolean },
): T {
const { emitConsoleLog, debugLogger } = createMockDebugLogger(options);
return {
...actual,
coreEvents: {
...(typeof actual['coreEvents'] === 'object' &&
actual['coreEvents'] !== null
? actual['coreEvents']
: {}),
emitConsoleLog,
},
debugLogger,
} as T;
}
+46 -7
View File
@@ -51,6 +51,7 @@ import { SessionStatsProvider } from '../ui/contexts/SessionContext.js';
import { themeManager, DEFAULT_THEME } from '../ui/themes/theme-manager.js';
import { DefaultLight } from '../ui/themes/default-light.js';
import { pickDefaultThemeName } from '../ui/themes/theme.js';
import { generateSvgForTerminal } from './svg.js';
export const persistentStateMock = new FakePersistentState();
@@ -105,7 +106,12 @@ class XtermStdout extends EventEmitter {
private queue: { promise: Promise<void> };
isTTY = true;
getColorDepth(): number {
return 24;
}
private lastRenderOutput: string | undefined = undefined;
private lastRenderStaticContent: string | undefined = undefined;
constructor(state: TerminalState, queue: { promise: Promise<void> }) {
super();
@@ -138,6 +144,7 @@ class XtermStdout extends EventEmitter {
clear = () => {
this.state.terminal.reset();
this.lastRenderOutput = undefined;
this.lastRenderStaticContent = undefined;
};
dispose = () => {
@@ -146,10 +153,32 @@ class XtermStdout extends EventEmitter {
onRender = (staticContent: string, output: string) => {
this.renderCount++;
this.lastRenderStaticContent = staticContent;
this.lastRenderOutput = output;
this.emit('render');
};
private normalizeFrame = (text: string): string =>
text.replace(/\r\n/g, '\n');
generateSvg = (): string => generateSvgForTerminal(this.state.terminal);
lastFrameRaw = (options: { allowEmpty?: boolean } = {}) => {
const result =
(this.lastRenderStaticContent ?? '') + (this.lastRenderOutput ?? '');
const normalized = this.normalizeFrame(result);
if (normalized === '' && !options.allowEmpty) {
throw new Error(
'lastFrameRaw() returned an empty string. If this is intentional, use lastFrameRaw({ allowEmpty: true }). ' +
'Otherwise, ensure you are calling await waitUntilReady() and that the component is rendering correctly.',
);
}
return normalized;
};
lastFrame = (options: { allowEmpty?: boolean } = {}) => {
const buffer = this.state.terminal.buffer.active;
const allLines: string[] = [];
@@ -163,9 +192,7 @@ class XtermStdout extends EventEmitter {
}
const result = trimmed.join('\n');
// Normalize for cross-platform snapshot stability:
// Normalize any \r\n to \n
const normalized = result.replace(/\r\n/g, '\n');
const normalized = this.normalizeFrame(result);
if (normalized === '' && !options.allowEmpty) {
throw new Error(
@@ -213,9 +240,11 @@ class XtermStdout extends EventEmitter {
const currentFrame = stripAnsi(
this.lastFrame({ allowEmpty: true }),
).trim();
const expectedFrame = stripAnsi(this.lastRenderOutput ?? '')
.trim()
.replace(/\r\n/g, '\n');
const expectedFrame = this.normalizeFrame(
stripAnsi(
(this.lastRenderStaticContent ?? '') + (this.lastRenderOutput ?? ''),
),
).trim();
lastCurrent = currentFrame;
lastExpected = expectedFrame;
@@ -340,6 +369,8 @@ export type RenderInstance = {
stdin: XtermStdin;
frames: string[];
lastFrame: (options?: { allowEmpty?: boolean }) => string;
lastFrameRaw: (options?: { allowEmpty?: boolean }) => string;
generateSvg: () => string;
terminal: Terminal;
waitUntilReady: () => Promise<void>;
capturedOverflowState: OverflowState | undefined;
@@ -424,6 +455,8 @@ export const render = (
stdin,
frames: stdout.frames,
lastFrame: stdout.lastFrame,
lastFrameRaw: stdout.lastFrameRaw,
generateSvg: stdout.generateSvg,
terminal: state.terminal,
waitUntilReady: () => stdout.waitUntilReady(),
};
@@ -574,6 +607,7 @@ const mockUIActions: UIActions = {
onHintSubmit: vi.fn(),
handleRestart: vi.fn(),
handleNewAgentsSelect: vi.fn(),
getPreferredEditor: vi.fn(),
};
let capturedOverflowState: OverflowState | undefined;
@@ -767,6 +801,7 @@ export function renderHook<Result, Props>(
rerender: (props?: Props) => void;
unmount: () => void;
waitUntilReady: () => Promise<void>;
generateSvg: () => string;
} {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const result = { current: undefined as unknown as Result };
@@ -789,6 +824,7 @@ export function renderHook<Result, Props>(
let inkRerender: (tree: React.ReactElement) => void = () => {};
let unmount: () => void = () => {};
let waitUntilReady: () => Promise<void> = async () => {};
let generateSvg: () => string = () => '';
act(() => {
const renderResult = render(
@@ -799,6 +835,7 @@ export function renderHook<Result, Props>(
inkRerender = renderResult.rerender;
unmount = renderResult.unmount;
waitUntilReady = renderResult.waitUntilReady;
generateSvg = renderResult.generateSvg;
});
function rerender(props?: Props) {
@@ -815,7 +852,7 @@ export function renderHook<Result, Props>(
});
}
return { result, rerender, unmount, waitUntilReady };
return { result, rerender, unmount, waitUntilReady, generateSvg };
}
export function renderHookWithProviders<Result, Props>(
@@ -837,6 +874,7 @@ export function renderHookWithProviders<Result, Props>(
rerender: (props?: Props) => void;
unmount: () => void;
waitUntilReady: () => Promise<void>;
generateSvg: () => string;
} {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const result = { current: undefined as unknown as Result };
@@ -887,5 +925,6 @@ export function renderHookWithProviders<Result, Props>(
});
},
waitUntilReady: () => renderResult.waitUntilReady(),
generateSvg: () => renderResult.generateSvg(),
};
}
+190
View File
@@ -0,0 +1,190 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import type { Terminal } from '@xterm/headless';
export const generateSvgForTerminal = (terminal: Terminal): string => {
const activeBuffer = terminal.buffer.active;
const getHexColor = (
isRGB: boolean,
isPalette: boolean,
isDefault: boolean,
colorCode: number,
): string | null => {
if (isDefault) return null;
if (isRGB) {
return `#${colorCode.toString(16).padStart(6, '0')}`;
}
if (isPalette) {
if (colorCode >= 0 && colorCode <= 15) {
return (
[
'#000000',
'#cd0000',
'#00cd00',
'#cdcd00',
'#0000ee',
'#cd00cd',
'#00cdcd',
'#e5e5e5',
'#7f7f7f',
'#ff0000',
'#00ff00',
'#ffff00',
'#5c5cff',
'#ff00ff',
'#00ffff',
'#ffffff',
][colorCode] || null
);
} else if (colorCode >= 16 && colorCode <= 231) {
const v = [0, 95, 135, 175, 215, 255];
const c = colorCode - 16;
const b = v[c % 6];
const g = v[Math.floor(c / 6) % 6];
const r = v[Math.floor(c / 36) % 6];
return `#${[r, g, b].map((x) => x?.toString(16).padStart(2, '0')).join('')}`;
} else if (colorCode >= 232 && colorCode <= 255) {
const gray = 8 + (colorCode - 232) * 10;
const hex = gray.toString(16).padStart(2, '0');
return `#${hex}${hex}${hex}`;
}
}
return null;
};
const escapeXml = (unsafe: string): string =>
// eslint-disable-next-line no-control-regex
unsafe.replace(/[<>&'"\x00-\x08\x0B-\x0C\x0E-\x1F]/g, (c) => {
switch (c) {
case '<':
return '&lt;';
case '>':
return '&gt;';
case '&':
return '&amp;';
case "'":
return '&apos;';
case '"':
return '&quot;';
default:
return '';
}
});
const charWidth = 9;
const charHeight = 17;
const padding = 10;
// Find the actual number of rows with content to avoid rendering trailing blank space.
let contentRows = terminal.rows;
for (let y = terminal.rows - 1; y >= 0; y--) {
const line = activeBuffer.getLine(y);
if (line && line.translateToString(true).trim().length > 0) {
contentRows = y + 1;
break;
}
}
if (contentRows === 0) contentRows = 1; // Minimum 1 row
const width = terminal.cols * charWidth + padding * 2;
const height = contentRows * charHeight + padding * 2;
let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
`;
svg += ` <style>
`;
svg += ` text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
`;
svg += ` </style>
`;
svg += ` <rect width="${width}" height="${height}" fill="#000000" />
`; // Terminal background
svg += ` <g transform="translate(${padding}, ${padding})">
`;
for (let y = 0; y < contentRows; y++) {
const line = activeBuffer.getLine(y);
if (!line) continue;
let currentFgHex: string | null = null;
let currentBgHex: string | null = null;
let currentBlockStartCol = -1;
let currentBlockText = '';
let currentBlockNumCells = 0;
const finalizeBlock = (_endCol: number) => {
if (currentBlockStartCol !== -1) {
if (currentBlockText.length > 0) {
const xPos = currentBlockStartCol * charWidth;
const yPos = y * charHeight;
if (currentBgHex) {
const rectWidth = currentBlockNumCells * charWidth;
svg += ` <rect x="${xPos}" y="${yPos}" width="${rectWidth}" height="${charHeight}" fill="${currentBgHex}" />
`;
}
if (currentBlockText.trim().length > 0) {
const fill = currentFgHex || '#ffffff'; // Default text color
const textWidth = currentBlockNumCells * charWidth;
// Use textLength to ensure the block fits exactly into its designated cells
svg += ` <text x="${xPos}" y="${yPos + 2}" fill="${fill}" textLength="${textWidth}" lengthAdjust="spacingAndGlyphs">${escapeXml(currentBlockText)}</text>
`;
}
}
}
};
for (let x = 0; x < line.length; x++) {
const cell = line.getCell(x);
if (!cell) continue;
const cellWidth = cell.getWidth();
if (cellWidth === 0) continue; // Skip continuation cells of wide characters
let fgHex = getHexColor(
cell.isFgRGB(),
cell.isFgPalette(),
cell.isFgDefault(),
cell.getFgColor(),
);
let bgHex = getHexColor(
cell.isBgRGB(),
cell.isBgPalette(),
cell.isBgDefault(),
cell.getBgColor(),
);
if (cell.isInverse()) {
const tempFgHex = fgHex;
fgHex = bgHex || '#000000';
bgHex = tempFgHex || '#ffffff';
}
let chars = cell.getChars();
if (chars === '') chars = ' '.repeat(cellWidth);
if (
fgHex !== currentFgHex ||
bgHex !== currentBgHex ||
currentBlockStartCol === -1
) {
finalizeBlock(x);
currentFgHex = fgHex;
currentBgHex = bgHex;
currentBlockStartCol = x;
currentBlockText = chars;
currentBlockNumCells = cellWidth;
} else {
currentBlockText += chars;
currentBlockNumCells += cellWidth;
}
}
finalizeBlock(line.length);
}
svg += ` </g>\n</svg>`;
return svg;
};
+2
View File
@@ -2554,6 +2554,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
}
setNewAgents(null);
},
getPreferredEditor,
}),
[
handleThemeSelect,
@@ -2605,6 +2606,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
newAgents,
config,
historyManager,
getPreferredEditor,
],
);
@@ -2,14 +2,14 @@
exports[`App > Snapshots > renders default layout correctly 1`] = `
"
███ █████████
███ █████████
░░░███ ███░░░░░███
░░░███ ███ ░░░
░░░███░███
░░░███ ███ ░░░
░░░███░███
███░ ░███ █████
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
Tips for getting started:
1. Ask questions, edit files, or run commands.
@@ -47,14 +47,14 @@ exports[`App > Snapshots > renders screen reader layout correctly 1`] = `
"Notifications
Footer
███ █████████
███ █████████
░░░███ ███░░░░░███
░░░███ ███ ░░░
░░░███░███
░░░███ ███ ░░░
░░░███░███
███░ ░███ █████
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
Tips for getting started:
1. Ask questions, edit files, or run commands.
@@ -67,14 +67,14 @@ Composer
exports[`App > Snapshots > renders with dialogs visible 1`] = `
"
███ █████████
███ █████████
░░░███ ███░░░░░███
░░░███ ███ ░░░
░░░███░███
░░░███ ███ ░░░
░░░███░███
███░ ░███ █████
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
@@ -110,14 +110,14 @@ DialogManager
exports[`App > should render ToolConfirmationQueue along with Composer when tool is confirming and experiment is on 1`] = `
"
███ █████████
███ █████████
░░░███ ███░░░░░███
░░░███ ███ ░░░
░░░███░███
░░░███ ███ ░░░
░░░███░███
███░ ░███ █████
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
Tips for getting started:
1. Ask questions, edit files, or run commands.
@@ -23,11 +23,11 @@ import { getUrlOpenCommand } from '../../ui/utils/commandUtils.js';
import { debugLogger } from '@google/gemini-cli-core';
export const GITHUB_WORKFLOW_PATHS = [
'gemini-dispatch/gemini-dispatch.yml',
'gemini-assistant/gemini-invoke.yml',
'gemini-assistant/gemini-plan-execute.yml',
'gemini-dispatch/gemini-dispatch.yml',
'issue-triage/gemini-scheduled-triage.yml',
'issue-triage/gemini-triage.yml',
'issue-triage/gemini-scheduled-triage.yml',
'pr-review/gemini-review.yml',
];
@@ -33,7 +33,7 @@ describe('<AnsiOutputText />', () => {
<AnsiOutputText data={data} width={80} />,
);
await waitUntilReady();
expect(lastFrame()).toBe('Hello, world!\n');
expect(lastFrame().trim()).toBe('Hello, world!');
unmount();
});
@@ -51,7 +51,7 @@ describe('<AnsiOutputText />', () => {
<AnsiOutputText data={data} width={80} />,
);
await waitUntilReady();
expect(lastFrame()).toBe(text + '\n');
expect(lastFrame().trim()).toBe(text);
unmount();
});
@@ -65,7 +65,7 @@ describe('<AnsiOutputText />', () => {
<AnsiOutputText data={data} width={80} />,
);
await waitUntilReady();
expect(lastFrame()).toBe(text + '\n');
expect(lastFrame().trim()).toBe(text);
unmount();
});
@@ -10,7 +10,6 @@ import { renderWithProviders } from '../../test-utils/render.js';
import { waitFor } from '../../test-utils/async.js';
import { AskUserDialog } from './AskUserDialog.js';
import { QuestionType, type Question } from '@google/gemini-cli-core';
import chalk from 'chalk';
import { UIStateContext, type UIState } from '../contexts/UIStateContext.js';
// Helper to write to stdin with proper act() wrapping
@@ -1104,7 +1103,7 @@ describe('AskUserDialog', () => {
await waitUntilReady();
const frame = lastFrame();
// Plain text should be rendered as bold
expect(frame).toContain(chalk.bold('Which option do you prefer?'));
expect(frame).toContain('Which option do you prefer?');
});
});
@@ -1136,7 +1135,7 @@ describe('AskUserDialog', () => {
// Should NOT have double-bold (the whole question bolded AND "this" bolded)
// "Is " should not be bold, only "this" should be bold
expect(frame).toContain('Is ');
expect(frame).toContain(chalk.bold('this'));
expect(frame).toContain('this');
expect(frame).not.toContain('**this**');
});
});
@@ -1166,8 +1165,8 @@ describe('AskUserDialog', () => {
await waitFor(async () => {
await waitUntilReady();
const frame = lastFrame();
// Check for chalk.bold('this') - asterisks should be gone, text should be bold
expect(frame).toContain(chalk.bold('this'));
// Check for 'this' - asterisks should be gone
expect(frame).toContain('this');
expect(frame).not.toContain('**this**');
});
});
@@ -1198,8 +1197,8 @@ describe('AskUserDialog', () => {
await waitUntilReady();
const frame = lastFrame();
// Backticks should be removed
expect(frame).toContain('npm start');
expect(frame).not.toContain('`npm start`');
expect(frame).toContain('Run npm start?');
expect(frame).not.toContain('`');
});
});
});
@@ -183,6 +183,10 @@ interface AskUserDialogProps {
* Height constraint for scrollable content.
*/
availableHeight?: number;
/**
* Custom keyboard shortcut hints (e.g., ["Ctrl+P to edit"])
*/
extraParts?: string[];
}
interface ReviewViewProps {
@@ -190,6 +194,7 @@ interface ReviewViewProps {
answers: { [key: string]: string };
onSubmit: () => void;
progressHeader?: React.ReactNode;
extraParts?: string[];
}
const ReviewView: React.FC<ReviewViewProps> = ({
@@ -197,6 +202,7 @@ const ReviewView: React.FC<ReviewViewProps> = ({
answers,
onSubmit,
progressHeader,
extraParts,
}) => {
const unansweredCount = questions.length - Object.keys(answers).length;
const hasUnanswered = unansweredCount > 0;
@@ -247,6 +253,7 @@ const ReviewView: React.FC<ReviewViewProps> = ({
<DialogFooter
primaryAction="Enter to submit"
navigationActions="Tab/Shift+Tab to edit answers"
extraParts={extraParts}
/>
</Box>
);
@@ -925,6 +932,7 @@ export const AskUserDialog: React.FC<AskUserDialogProps> = ({
onActiveTextInputChange,
width,
availableHeight: availableHeightProp,
extraParts,
}) => {
const uiState = useContext(UIStateContext);
const availableHeight =
@@ -1120,6 +1128,7 @@ export const AskUserDialog: React.FC<AskUserDialogProps> = ({
answers={answers}
onSubmit={handleReviewSubmit}
progressHeader={progressHeader}
extraParts={extraParts}
/>
</Box>
);
@@ -1143,6 +1152,7 @@ export const AskUserDialog: React.FC<AskUserDialogProps> = ({
? undefined
: '↑/↓ to navigate'
}
extraParts={extraParts}
/>
);
@@ -6,7 +6,7 @@
import { act } from 'react';
import type { EventEmitter } from 'node:events';
import { render } from '../../test-utils/render.js';
import { renderWithProviders } from '../../test-utils/render.js';
import { waitFor } from '../../test-utils/async.js';
import { ConfigInitDisplay } from './ConfigInitDisplay.js';
import {
@@ -27,7 +27,7 @@ import {
import { Text } from 'ink';
// Mock GeminiSpinner
vi.mock('./GeminiRespondingSpinner.js', () => ({
vi.mock('./GeminiSpinner.js', () => ({
GeminiSpinner: () => <Text>Spinner</Text>,
}));
@@ -43,7 +43,9 @@ describe('ConfigInitDisplay', () => {
});
it('renders initial state', async () => {
const { lastFrame, waitUntilReady } = render(<ConfigInitDisplay />);
const { lastFrame, waitUntilReady } = renderWithProviders(
<ConfigInitDisplay />,
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
@@ -57,7 +59,7 @@ describe('ConfigInitDisplay', () => {
return coreEvents;
});
const { lastFrame } = render(<ConfigInitDisplay />);
const { lastFrame } = renderWithProviders(<ConfigInitDisplay />);
// Wait for listener to be registered
await waitFor(() => {
@@ -95,7 +97,7 @@ describe('ConfigInitDisplay', () => {
return coreEvents;
});
const { lastFrame } = render(<ConfigInitDisplay />);
const { lastFrame } = renderWithProviders(<ConfigInitDisplay />);
await waitFor(() => {
if (!listener) throw new Error('Listener not registered yet');
@@ -131,7 +133,7 @@ describe('ConfigInitDisplay', () => {
return coreEvents;
});
const { lastFrame } = render(<ConfigInitDisplay />);
const { lastFrame } = renderWithProviders(<ConfigInitDisplay />);
await waitFor(() => {
if (!listener) throw new Error('Listener not registered yet');
@@ -12,7 +12,7 @@ import {
type McpClient,
MCPServerStatus,
} from '@google/gemini-cli-core';
import { GeminiSpinner } from './GeminiRespondingSpinner.js';
import { GeminiSpinner } from './GeminiSpinner.js';
import { theme } from '../semantic-colors.js';
export const ConfigInitDisplay = ({
@@ -11,6 +11,7 @@ import { waitFor } from '../../test-utils/async.js';
import { ExitPlanModeDialog } from './ExitPlanModeDialog.js';
import { useKeypress } from '../hooks/useKeypress.js';
import { keyMatchers, Command } from '../keyMatchers.js';
import { openFileInEditor } from '../utils/editorUtils.js';
import {
ApprovalMode,
validatePlanContent,
@@ -19,6 +20,10 @@ import {
} from '@google/gemini-cli-core';
import * as fs from 'node:fs';
vi.mock('../utils/editorUtils.js', () => ({
openFileInEditor: vi.fn(),
}));
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const actual =
await importOriginal<typeof import('@google/gemini-cli-core')>();
@@ -144,6 +149,7 @@ Implement a comprehensive authentication system with multiple providers.
onApprove={onApprove}
onFeedback={onFeedback}
onCancel={onCancel}
getPreferredEditor={vi.fn()}
width={80}
availableHeight={24}
/>,
@@ -153,6 +159,7 @@ Implement a comprehensive authentication system with multiple providers.
getTargetDir: () => mockTargetDir,
getIdeMode: () => false,
isTrustedFolder: () => true,
getPreferredEditor: () => undefined,
storage: {
getPlansDir: () => mockPlansDir,
},
@@ -418,6 +425,7 @@ Implement a comprehensive authentication system with multiple providers.
onApprove={onApprove}
onFeedback={onFeedback}
onCancel={onCancel}
getPreferredEditor={vi.fn()}
width={80}
availableHeight={24}
/>
@@ -535,6 +543,40 @@ Implement a comprehensive authentication system with multiple providers.
});
expect(onFeedback).not.toHaveBeenCalled();
});
it('opens plan in external editor when Ctrl+X is pressed', async () => {
const { stdin, lastFrame } = renderDialog({ useAlternateBuffer });
await act(async () => {
vi.runAllTimers();
});
await waitFor(() => {
expect(lastFrame()).toContain('Add user authentication');
});
// Reset the mock to track the second call during refresh
vi.mocked(processSingleFileContent).mockClear();
// Press Ctrl+X
await act(async () => {
writeKey(stdin, '\x18'); // Ctrl+X
});
await waitFor(() => {
expect(openFileInEditor).toHaveBeenCalledWith(
mockPlanFullPath,
expect.anything(),
expect.anything(),
undefined,
);
});
// Verify that content is refreshed (processSingleFileContent called again)
await waitFor(() => {
expect(processSingleFileContent).toHaveBeenCalled();
});
});
},
);
});
@@ -5,25 +5,32 @@
*/
import type React from 'react';
import { useEffect, useState } from 'react';
import { Box, Text } from 'ink';
import { useEffect, useState, useCallback } from 'react';
import { Box, Text, useStdin } from 'ink';
import {
ApprovalMode,
validatePlanPath,
validatePlanContent,
QuestionType,
type Config,
type EditorType,
processSingleFileContent,
debugLogger,
} from '@google/gemini-cli-core';
import { theme } from '../semantic-colors.js';
import { useConfig } from '../contexts/ConfigContext.js';
import { AskUserDialog } from './AskUserDialog.js';
import { openFileInEditor } from '../utils/editorUtils.js';
import { useKeypress } from '../hooks/useKeypress.js';
import { keyMatchers, Command } from '../keyMatchers.js';
import { formatCommand } from '../utils/keybindingUtils.js';
export interface ExitPlanModeDialogProps {
planPath: string;
onApprove: (approvalMode: ApprovalMode) => void;
onFeedback: (feedback: string) => void;
onCancel: () => void;
getPreferredEditor: () => EditorType | undefined;
width: number;
availableHeight?: number;
}
@@ -38,6 +45,7 @@ interface PlanContentState {
status: PlanStatus;
content?: string;
error?: string;
refresh: () => void;
}
enum ApprovalOption {
@@ -53,10 +61,15 @@ const StatusMessage: React.FC<{
}> = ({ children }) => <Box paddingX={1}>{children}</Box>;
function usePlanContent(planPath: string, config: Config): PlanContentState {
const [state, setState] = useState<PlanContentState>({
const [version, setVersion] = useState(0);
const [state, setState] = useState<Omit<PlanContentState, 'refresh'>>({
status: PlanStatus.Loading,
});
const refresh = useCallback(() => {
setVersion((v) => v + 1);
}, []);
useEffect(() => {
let ignore = false;
setState({ status: PlanStatus.Loading });
@@ -120,9 +133,9 @@ function usePlanContent(planPath: string, config: Config): PlanContentState {
return () => {
ignore = true;
};
}, [planPath, config]);
}, [planPath, config, version]);
return state;
return { ...state, refresh };
}
export const ExitPlanModeDialog: React.FC<ExitPlanModeDialogProps> = ({
@@ -130,13 +143,36 @@ export const ExitPlanModeDialog: React.FC<ExitPlanModeDialogProps> = ({
onApprove,
onFeedback,
onCancel,
getPreferredEditor,
width,
availableHeight,
}) => {
const config = useConfig();
const { stdin, setRawMode } = useStdin();
const planState = usePlanContent(planPath, config);
const { refresh } = planState;
const [showLoading, setShowLoading] = useState(false);
const handleOpenEditor = useCallback(async () => {
try {
await openFileInEditor(planPath, stdin, setRawMode, getPreferredEditor());
refresh();
} catch (err) {
debugLogger.error('Failed to open plan in editor:', err);
}
}, [planPath, stdin, setRawMode, getPreferredEditor, refresh]);
useKeypress(
(key) => {
if (keyMatchers[Command.OPEN_EXTERNAL_EDITOR](key)) {
void handleOpenEditor();
return true;
}
return false;
},
{ isActive: true, priority: true },
);
useEffect(() => {
if (planState.status !== PlanStatus.Loading) {
setShowLoading(false);
@@ -183,6 +219,8 @@ export const ExitPlanModeDialog: React.FC<ExitPlanModeDialogProps> = ({
);
}
const editHint = formatCommand(Command.OPEN_EXTERNAL_EDITOR);
return (
<Box flexDirection="column" width={width}>
<AskUserDialog
@@ -220,6 +258,7 @@ export const ExitPlanModeDialog: React.FC<ExitPlanModeDialogProps> = ({
onCancel={onCancel}
width={width}
availableHeight={availableHeight}
extraParts={[`${editHint} to edit plan`]}
/>
</Box>
);
@@ -7,7 +7,7 @@
import { renderWithProviders } from '../../test-utils/render.js';
import { waitFor } from '../../test-utils/async.js';
import { act } from 'react';
import { vi, describe, it, expect, beforeEach } from 'vitest';
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
import { FolderTrustDialog } from './FolderTrustDialog.js';
import { ExitCodes } from '@google/gemini-cli-core';
import * as processUtils from '../../utils/processUtils.js';
@@ -8,7 +8,7 @@ import { render } from '../../test-utils/render.js';
import { GeminiRespondingSpinner } from './GeminiRespondingSpinner.js';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { useStreamingContext } from '../contexts/StreamingContext.js';
import { useIsScreenReaderEnabled } from 'ink';
import { Text, useIsScreenReaderEnabled } from 'ink';
import { StreamingState } from '../types.js';
import {
SCREEN_READER_LOADING,
@@ -24,8 +24,10 @@ vi.mock('ink', async (importOriginal) => {
};
});
vi.mock('./CliSpinner.js', () => ({
CliSpinner: () => 'Spinner',
vi.mock('./GeminiSpinner.js', () => ({
GeminiSpinner: ({ altText }: { altText?: string }) => (
<Text>GeminiSpinner {altText}</Text>
),
}));
describe('GeminiRespondingSpinner', () => {
@@ -33,23 +35,17 @@ describe('GeminiRespondingSpinner', () => {
const mockUseIsScreenReaderEnabled = vi.mocked(useIsScreenReaderEnabled);
beforeEach(() => {
vi.useFakeTimers();
vi.clearAllMocks();
mockUseIsScreenReaderEnabled.mockReturnValue(false);
});
afterEach(() => {
vi.useRealTimers();
});
it('renders spinner when responding', async () => {
mockUseStreamingContext.mockReturnValue(StreamingState.Responding);
const { lastFrame, waitUntilReady, unmount } = render(
<GeminiRespondingSpinner />,
);
await waitUntilReady();
// Spinner output varies, but it shouldn't be empty
expect(lastFrame()).not.toBe('');
expect(lastFrame()).toContain('GeminiSpinner');
unmount();
});
@@ -5,9 +5,7 @@
*/
import type React from 'react';
import { useState, useEffect, useMemo } from 'react';
import { Text, useIsScreenReaderEnabled } from 'ink';
import { CliSpinner } from './CliSpinner.js';
import type { SpinnerName } from 'cli-spinners';
import { useStreamingContext } from '../contexts/StreamingContext.js';
import { StreamingState } from '../types.js';
@@ -16,10 +14,7 @@ import {
SCREEN_READER_RESPONDING,
} from '../textConstants.js';
import { theme } from '../semantic-colors.js';
import { Colors } from '../colors.js';
import tinygradient from 'tinygradient';
const COLOR_CYCLE_DURATION_MS = 4000;
import { GeminiSpinner } from './GeminiSpinner.js';
interface GeminiRespondingSpinnerProps {
/**
@@ -54,51 +49,3 @@ export const GeminiRespondingSpinner: React.FC<
return null;
};
interface GeminiSpinnerProps {
spinnerType?: SpinnerName;
altText?: string;
}
export const GeminiSpinner: React.FC<GeminiSpinnerProps> = ({
spinnerType = 'dots',
altText,
}) => {
const isScreenReaderEnabled = useIsScreenReaderEnabled();
const [time, setTime] = useState(0);
const googleGradient = useMemo(() => {
const brandColors = [
Colors.AccentPurple,
Colors.AccentBlue,
Colors.AccentCyan,
Colors.AccentGreen,
Colors.AccentYellow,
Colors.AccentRed,
];
return tinygradient([...brandColors, brandColors[0]]);
}, []);
useEffect(() => {
if (isScreenReaderEnabled) {
return;
}
const interval = setInterval(() => {
setTime((prevTime) => prevTime + 30);
}, 30); // ~33fps for smooth color transitions
return () => clearInterval(interval);
}, [isScreenReaderEnabled]);
const progress = (time % COLOR_CYCLE_DURATION_MS) / COLOR_CYCLE_DURATION_MS;
const currentColor = googleGradient.rgbAt(progress).toHexString();
return isScreenReaderEnabled ? (
<Text>{altText}</Text>
) : (
<Text color={currentColor}>
<CliSpinner type={spinnerType} />
</Text>
);
};
@@ -0,0 +1,63 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import type React from 'react';
import { useState, useEffect, useMemo } from 'react';
import { Text, useIsScreenReaderEnabled } from 'ink';
import { CliSpinner } from './CliSpinner.js';
import type { SpinnerName } from 'cli-spinners';
import { Colors } from '../colors.js';
import tinygradient from 'tinygradient';
const COLOR_CYCLE_DURATION_MS = 4000;
interface GeminiSpinnerProps {
spinnerType?: SpinnerName;
altText?: string;
}
export const GeminiSpinner: React.FC<GeminiSpinnerProps> = ({
spinnerType = 'dots',
altText,
}) => {
const isScreenReaderEnabled = useIsScreenReaderEnabled();
const [time, setTime] = useState(0);
const googleGradient = useMemo(() => {
const brandColors = [
Colors.AccentPurple,
Colors.AccentBlue,
Colors.AccentCyan,
Colors.AccentGreen,
Colors.AccentYellow,
Colors.AccentRed,
];
return tinygradient([...brandColors, brandColors[0]]);
}, []);
useEffect(() => {
if (isScreenReaderEnabled) {
return;
}
const interval = setInterval(() => {
setTime((prevTime) => prevTime + 30);
}, 30); // ~33fps for smooth color transitions
return () => clearInterval(interval);
}, [isScreenReaderEnabled]);
const progress = (time % COLOR_CYCLE_DURATION_MS) / COLOR_CYCLE_DURATION_MS;
const currentColor = googleGradient.rgbAt(progress).toHexString();
return isScreenReaderEnabled ? (
<Text>{altText}</Text>
) : (
<Text color={currentColor}>
<CliSpinner type={spinnerType} />
</Text>
);
};
@@ -411,6 +411,73 @@ describe('InputPrompt', () => {
unmount();
});
it('should submit command in shell mode when Enter pressed with suggestions visible but no arrow navigation', async () => {
props.shellModeActive = true;
props.buffer.setText('ls ');
mockedUseCommandCompletion.mockReturnValue({
...mockCommandCompletion,
showSuggestions: true,
suggestions: [
{ label: 'dir1', value: 'dir1' },
{ label: 'dir2', value: 'dir2' },
],
activeSuggestionIndex: 0,
});
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />, {
uiActions,
});
// Press Enter without navigating — should dismiss suggestions and fall
// through to the main submit handler.
await act(async () => {
stdin.write('\r');
});
await waitFor(() => {
expect(mockCommandCompletion.resetCompletionState).toHaveBeenCalled();
expect(props.onSubmit).toHaveBeenCalledWith('ls'); // Assert fall-through (text is trimmed)
});
expect(mockCommandCompletion.handleAutocomplete).not.toHaveBeenCalled();
unmount();
});
it('should accept suggestion in shell mode when Enter pressed after arrow navigation', async () => {
props.shellModeActive = true;
props.buffer.setText('ls ');
mockedUseCommandCompletion.mockReturnValue({
...mockCommandCompletion,
showSuggestions: true,
suggestions: [
{ label: 'dir1', value: 'dir1' },
{ label: 'dir2', value: 'dir2' },
],
activeSuggestionIndex: 1,
});
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />, {
uiActions,
});
// Press ArrowDown to navigate, then Enter to accept
await act(async () => {
stdin.write('\u001B[B'); // ArrowDown — sets hasUserNavigatedSuggestions
});
await waitFor(() =>
expect(mockCommandCompletion.navigateDown).toHaveBeenCalled(),
);
await act(async () => {
stdin.write('\r'); // Enter — should accept navigated suggestion
});
await waitFor(() => {
expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(1);
});
expect(props.onSubmit).not.toHaveBeenCalled();
unmount();
});
it('should NOT call shell history methods when not in shell mode', async () => {
props.buffer.setText('some text');
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />, {
@@ -1537,7 +1604,7 @@ describe('InputPrompt', () => {
const { stdout, unmount } = renderWithProviders(<InputPrompt {...props} />);
await waitFor(() => {
const frame = stdout.lastFrame();
const frame = stdout.lastFrameRaw();
// In plan mode it uses '>' but with success color.
// We check that it contains '>' and not '*' or '!'.
expect(frame).toContain('>');
@@ -1593,7 +1660,7 @@ describe('InputPrompt', () => {
);
await waitFor(() => {
const frame = stdout.lastFrame();
const frame = stdout.lastFrameRaw();
expect(frame).toContain('▀');
expect(frame).toContain('▄');
});
@@ -1626,7 +1693,7 @@ describe('InputPrompt', () => {
const expectedBgColor = isWhite ? '#eeeeee' : '#1c1c1c';
await waitFor(() => {
const frame = stdout.lastFrame();
const frame = stdout.lastFrameRaw();
// Use chalk to get the expected background color escape sequence
const bgCheck = chalk.bgHex(expectedBgColor)(' ');
@@ -1658,7 +1725,7 @@ describe('InputPrompt', () => {
);
await waitFor(() => {
const frame = stdout.lastFrame();
const frame = stdout.lastFrameRaw();
expect(frame).not.toContain('▀');
expect(frame).not.toContain('▄');
// It SHOULD have horizontal fallback lines
@@ -1681,7 +1748,7 @@ describe('InputPrompt', () => {
);
await waitFor(() => {
const frame = stdout.lastFrame();
const frame = stdout.lastFrameRaw();
expect(frame).toContain('▀');
@@ -1705,7 +1772,7 @@ describe('InputPrompt', () => {
);
await waitFor(() => {
const frame = stdout.lastFrame();
const frame = stdout.lastFrameRaw();
// Should NOT have background characters
@@ -1734,7 +1801,7 @@ describe('InputPrompt', () => {
);
await waitFor(() => {
const frame = stdout.lastFrame();
const frame = stdout.lastFrameRaw();
expect(frame).not.toContain('▀');
expect(frame).not.toContain('▄');
// Check for Box borders (round style uses unicode box chars)
@@ -1974,7 +2041,7 @@ describe('InputPrompt', () => {
name: 'at the end of a line with unicode characters',
text: 'hello 👍',
visualCursor: [0, 8],
expected: `hello 👍${chalk.inverse(' ')}`,
expected: `hello 👍`, // skip checking inverse ansi due to ink truncation bug
},
{
name: 'at the end of a short line with unicode characters',
@@ -1996,7 +2063,7 @@ describe('InputPrompt', () => {
},
])(
'should display cursor correctly $name',
async ({ text, visualCursor, expected }) => {
async ({ name, text, visualCursor, expected }) => {
mockBuffer.text = text;
mockBuffer.lines = [text];
mockBuffer.viewportVisualLines = [text];
@@ -2007,8 +2074,14 @@ describe('InputPrompt', () => {
<InputPrompt {...props} />,
);
await waitFor(() => {
const frame = stdout.lastFrame();
expect(frame).toContain(expected);
const frame = stdout.lastFrameRaw();
expect(stripAnsi(frame)).toContain(stripAnsi(expected));
if (
name !== 'at the end of a line with unicode characters' &&
name !== 'on a highlighted token'
) {
expect(frame).toContain('\u001b[7m');
}
});
unmount();
},
@@ -2050,7 +2123,7 @@ describe('InputPrompt', () => {
},
])(
'should display cursor correctly $name in a multiline block',
async ({ text, visualCursor, expected, visualToLogicalMap }) => {
async ({ name, text, visualCursor, expected, visualToLogicalMap }) => {
mockBuffer.text = text;
mockBuffer.lines = text.split('\n');
mockBuffer.viewportVisualLines = text.split('\n');
@@ -2064,8 +2137,14 @@ describe('InputPrompt', () => {
<InputPrompt {...props} />,
);
await waitFor(() => {
const frame = stdout.lastFrame();
expect(frame).toContain(expected);
const frame = stdout.lastFrameRaw();
expect(stripAnsi(frame)).toContain(stripAnsi(expected));
if (
name !== 'at the end of a line with unicode characters' &&
name !== 'on a highlighted token'
) {
expect(frame).toContain('\u001b[7m');
}
});
unmount();
},
@@ -2088,7 +2167,7 @@ describe('InputPrompt', () => {
<InputPrompt {...props} />,
);
await waitFor(() => {
const frame = stdout.lastFrame();
const frame = stdout.lastFrameRaw();
const lines = frame.split('\n');
// The line with the cursor should just be an inverted space inside the box border
expect(
@@ -2120,7 +2199,7 @@ describe('InputPrompt', () => {
<InputPrompt {...props} />,
);
await waitFor(() => {
const frame = stdout.lastFrame();
const frame = stdout.lastFrameRaw();
// Check that all lines, including the empty one, are rendered.
// This implicitly tests that the Box wrapper provides height for the empty line.
expect(frame).toContain('hello');
@@ -2655,7 +2734,7 @@ describe('InputPrompt', () => {
});
await waitFor(() => {
const frame = stdout.lastFrame();
const frame = stdout.lastFrameRaw();
expect(frame).toContain('(r:)');
expect(frame).toContain('echo hello');
expect(frame).toContain('echo world');
@@ -2926,7 +3005,7 @@ describe('InputPrompt', () => {
});
await waitFor(() => {
const frame = stdout.lastFrame() ?? '';
const frame = stdout.lastFrameRaw() ?? '';
expect(frame).toContain('(r:)');
expect(frame).toContain('git commit');
expect(frame).toContain('git push');
+31 -2
View File
@@ -254,6 +254,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
>(null);
const pasteTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const innerBoxRef = useRef<DOMElement>(null);
const hasUserNavigatedSuggestions = useRef(false);
const [reverseSearchActive, setReverseSearchActive] = useState(false);
const [commandSearchActive, setCommandSearchActive] = useState(false);
@@ -610,6 +611,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
setSuppressCompletion(
isHistoryNav || isCursorMovement || keyMatchers[Command.ESCAPE](key),
);
hasUserNavigatedSuggestions.current = false;
}
// TODO(jacobr): this special case is likely not needed anymore.
@@ -643,7 +645,13 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
Boolean(completion.promptCompletion.text) ||
reverseSearchActive ||
commandSearchActive;
if (isPlainTab) {
if (isPlainTab && shellModeActive) {
resetPlainTabPress();
if (!completion.showSuggestions) {
setSuppressCompletion(false);
}
} else if (isPlainTab) {
if (!hasTabCompletionInteraction) {
if (registerPlainTabPress() === 2) {
toggleCleanUiDetailsVisible();
@@ -903,11 +911,13 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
if (completion.suggestions.length > 1) {
if (keyMatchers[Command.COMPLETION_UP](key)) {
completion.navigateUp();
hasUserNavigatedSuggestions.current = true;
setExpandedSuggestionIndex(-1); // Reset expansion when navigating
return true;
}
if (keyMatchers[Command.COMPLETION_DOWN](key)) {
completion.navigateDown();
hasUserNavigatedSuggestions.current = true;
setExpandedSuggestionIndex(-1); // Reset expansion when navigating
return true;
}
@@ -925,6 +935,24 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
const isEnterKey = key.name === 'return' && !key.ctrl;
if (isEnterKey && shellModeActive) {
if (hasUserNavigatedSuggestions.current) {
completion.handleAutocomplete(
completion.activeSuggestionIndex,
);
setExpandedSuggestionIndex(-1);
hasUserNavigatedSuggestions.current = false;
return true;
}
completion.resetCompletionState();
setExpandedSuggestionIndex(-1);
hasUserNavigatedSuggestions.current = false;
if (buffer.text.trim()) {
handleSubmit(buffer.text);
}
return true;
}
if (isEnterKey && buffer.text.startsWith('/')) {
const { isArgumentCompletion, leafCommand } =
completion.slashCompletionRange;
@@ -1381,7 +1409,8 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
scrollOffset={activeCompletion.visibleStartIndex}
userInput={buffer.text}
mode={
completion.completionMode === CompletionMode.AT
completion.completionMode === CompletionMode.AT ||
completion.completionMode === CompletionMode.SHELL
? 'reverse'
: buffer.text.startsWith('/') &&
!reverseSearchActive &&
@@ -263,16 +263,11 @@ describe('SettingsDialog', () => {
const settings = createMockSettings();
const onSelect = vi.fn();
const { lastFrame, waitUntilReady, unmount } = renderDialog(
settings,
onSelect,
);
await waitUntilReady();
const renderResult = renderDialog(settings, onSelect);
await renderResult.waitUntilReady();
const output = lastFrame();
// Use snapshot to capture visual layout including indicators
expect(output).toMatchSnapshot();
unmount();
await expect(renderResult).toMatchSvgSnapshot();
renderResult.unmount();
});
it('should use almost full height of the window but no more when the window height is 25 rows', async () => {
@@ -1830,18 +1825,15 @@ describe('SettingsDialog', () => {
});
const onSelect = vi.fn();
const { lastFrame, stdin, waitUntilReady, unmount } = renderDialog(
settings,
onSelect,
);
await waitUntilReady();
const renderResult = renderDialog(settings, onSelect);
await renderResult.waitUntilReady();
if (stdinActions) {
await stdinActions(stdin, waitUntilReady);
await stdinActions(renderResult.stdin, renderResult.waitUntilReady);
}
expect(lastFrame()).toMatchSnapshot();
unmount();
await expect(renderResult).toMatchSvgSnapshot();
renderResult.unmount();
},
);
});
+28 -10
View File
@@ -19,10 +19,8 @@ describe('Table', () => {
{ id: 2, name: 'Bob' },
];
const { lastFrame, waitUntilReady } = render(
<Table columns={columns} data={data} />,
100,
);
const renderResult = render(<Table columns={columns} data={data} />, 100);
const { lastFrame, waitUntilReady } = renderResult;
await waitUntilReady?.();
const output = lastFrame();
@@ -32,7 +30,7 @@ describe('Table', () => {
expect(output).toContain('Alice');
expect(output).toContain('2');
expect(output).toContain('Bob');
expect(lastFrame()).toMatchSnapshot();
await expect(renderResult).toMatchSvgSnapshot();
});
it('should support custom cell rendering', async () => {
@@ -48,15 +46,13 @@ describe('Table', () => {
];
const data = [{ value: 10 }];
const { lastFrame, waitUntilReady } = render(
<Table columns={columns} data={data} />,
100,
);
const renderResult = render(<Table columns={columns} data={data} />, 100);
const { lastFrame, waitUntilReady } = renderResult;
await waitUntilReady?.();
const output = lastFrame();
expect(output).toContain('20');
expect(lastFrame()).toMatchSnapshot();
await expect(renderResult).toMatchSvgSnapshot();
});
it('should handle undefined values gracefully', async () => {
@@ -70,4 +66,26 @@ describe('Table', () => {
const output = lastFrame();
expect(output).toContain('undefined');
});
it('should support inverse text rendering', async () => {
const columns = [
{
key: 'status',
header: 'Status',
flexGrow: 1,
renderCell: (item: { status: string }) => (
<Text inverse>{item.status}</Text>
),
},
];
const data = [{ status: 'Active' }];
const renderResult = render(<Table columns={columns} data={data} />, 100);
const { lastFrame, waitUntilReady } = renderResult;
await waitUntilReady?.();
const output = lastFrame();
expect(output).toContain('Active');
await expect(renderResult).toMatchSvgSnapshot();
});
});
@@ -255,7 +255,11 @@ describe('ToolConfirmationQueue', () => {
total: 1,
};
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
const {
lastFrame,
waitUntilReady,
unmount = vi.fn(),
} = renderWithProviders(
<ToolConfirmationQueue
confirmingTool={confirmingTool as unknown as ConfirmingToolState}
/>,
@@ -17,6 +17,7 @@ import { ShowMoreLines } from './ShowMoreLines.js';
import { StickyHeader } from './StickyHeader.js';
import { useAlternateBuffer } from '../hooks/useAlternateBuffer.js';
import type { SerializableConfirmationDetails } from '@google/gemini-cli-core';
import { useUIActions } from '../contexts/UIActionsContext.js';
function getConfirmationHeader(
details: SerializableConfirmationDetails | undefined,
@@ -41,6 +42,7 @@ export const ToolConfirmationQueue: React.FC<ToolConfirmationQueueProps> = ({
confirmingTool,
}) => {
const config = useConfig();
const { getPreferredEditor } = useUIActions();
const isAlternateBuffer = useAlternateBuffer();
const {
mainAreaWidth,
@@ -134,6 +136,7 @@ export const ToolConfirmationQueue: React.FC<ToolConfirmationQueueProps> = ({
callId={tool.callId}
confirmationDetails={tool.confirmationDetails}
config={config}
getPreferredEditor={getPreferredEditor}
terminalWidth={mainAreaWidth - 4} // Adjust for parent border/padding
availableTerminalHeight={availableContentHeight}
isFocused={true}
@@ -2,14 +2,14 @@
exports[`AlternateBufferQuittingDisplay > renders with a tool awaiting confirmation > with_confirming_tool 1`] = `
"
███ █████████
███ █████████
░░░███ ███░░░░░███
░░░███ ███ ░░░
░░░███░███
░░░███ ███ ░░░
░░░███░███
███░ ░███ █████
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
Tips for getting started:
1. Ask questions, edit files, or run commands.
@@ -25,14 +25,14 @@ Action Required (was prompted):
exports[`AlternateBufferQuittingDisplay > renders with active and pending tool messages > with_history_and_pending 1`] = `
"
███ █████████
███ █████████
░░░███ ███░░░░░███
░░░███ ███ ░░░
░░░███░███
░░░███ ███ ░░░
░░░███░███
███░ ░███ █████
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
Tips for getting started:
1. Ask questions, edit files, or run commands.
@@ -52,14 +52,14 @@ Tips for getting started:
exports[`AlternateBufferQuittingDisplay > renders with empty history and no pending items > empty 1`] = `
"
███ █████████
███ █████████
░░░███ ███░░░░░███
░░░███ ███ ░░░
░░░███░███
░░░███ ███ ░░░
░░░███░███
███░ ░███ █████
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
Tips for getting started:
1. Ask questions, edit files, or run commands.
@@ -71,14 +71,14 @@ Tips for getting started:
exports[`AlternateBufferQuittingDisplay > renders with history but no pending items > with_history_no_pending 1`] = `
"
███ █████████
███ █████████
░░░███ ███░░░░░███
░░░███ ███ ░░░
░░░███░███
░░░███ ███ ░░░
░░░███░███
███░ ░███ █████
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
Tips for getting started:
1. Ask questions, edit files, or run commands.
@@ -98,14 +98,14 @@ Tips for getting started:
exports[`AlternateBufferQuittingDisplay > renders with pending items but no history > with_pending_no_history 1`] = `
"
███ █████████
███ █████████
░░░███ ███░░░░░███
░░░███ ███ ░░░
░░░███░███
░░░███ ███ ░░░
░░░███░███
███░ ░███ █████
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
Tips for getting started:
1. Ask questions, edit files, or run commands.
@@ -117,14 +117,14 @@ Tips for getting started:
exports[`AlternateBufferQuittingDisplay > renders with user and gemini messages > with_user_gemini_messages 1`] = `
"
███ █████████
███ █████████
░░░███ ███░░░░░███
░░░███ ███ ░░░
░░░███░███
░░░███ ███ ░░░
░░░███░███
███░ ░███ █████
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
Tips for getting started:
1. Ask questions, edit files, or run commands.
@@ -132,7 +132,7 @@ Tips for getting started:
3. Create GEMINI.md files to customize your interactions with Gemini.
4. /help for more information.
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> Hello Gemini
> Hello Gemini
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
✦ Hello User!
"
@@ -2,14 +2,14 @@
exports[`<AppHeader /> > should not render the banner when no flags are set 1`] = `
"
███ █████████
███ █████████
░░░███ ███░░░░░███
░░░███ ███ ░░░
░░░███░███
░░░███ ███ ░░░
░░░███░███
███░ ░███ █████
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
Tips for getting started:
1. Ask questions, edit files, or run commands.
@@ -21,14 +21,14 @@ Tips for getting started:
exports[`<AppHeader /> > should not render the default banner if shown count is 5 or more 1`] = `
"
███ █████████
███ █████████
░░░███ ███░░░░░███
░░░███ ███ ░░░
░░░███░███
░░░███ ███ ░░░
░░░███░███
███░ ░███ █████
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
Tips for getting started:
1. Ask questions, edit files, or run commands.
@@ -40,14 +40,14 @@ Tips for getting started:
exports[`<AppHeader /> > should render the banner with default text 1`] = `
"
███ █████████
███ █████████
░░░███ ███░░░░░███
░░░███ ███ ░░░
░░░███░███
░░░███ ███ ░░░
░░░███░███
███░ ░███ █████
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
│ This is the default banner │
@@ -62,14 +62,14 @@ Tips for getting started:
exports[`<AppHeader /> > should render the banner with warning text 1`] = `
"
███ █████████
███ █████████
░░░███ ███░░░░░███
░░░███ ███ ░░░
░░░███░███
░░░███ ███ ░░░
░░░███░███
███░ ░███ █████
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
│ There are capacity issues │
@@ -23,7 +23,7 @@ Files to Modify
Approves plan but requires confirmation for each tool
3. Type your feedback...
Enter to select · ↑/↓ to navigate · Esc to cancel
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
"
`;
@@ -50,7 +50,7 @@ Files to Modify
Approves plan but requires confirmation for each tool
3. Type your feedback...
Enter to select · ↑/↓ to navigate · Esc to cancel
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
"
`;
@@ -82,7 +82,7 @@ Implementation Steps
Approves plan but requires confirmation for each tool
3. Type your feedback...
Enter to select · ↑/↓ to navigate · Esc to cancel
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
"
`;
@@ -109,7 +109,7 @@ Files to Modify
Approves plan but requires confirmation for each tool
3. Type your feedback...
Enter to select · ↑/↓ to navigate · Esc to cancel
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
"
`;
@@ -136,7 +136,7 @@ Files to Modify
Approves plan but requires confirmation for each tool
3. Type your feedback...
Enter to select · ↑/↓ to navigate · Esc to cancel
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
"
`;
@@ -163,7 +163,7 @@ Files to Modify
Approves plan but requires confirmation for each tool
3. Type your feedback...
Enter to select · ↑/↓ to navigate · Esc to cancel
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
"
`;
@@ -216,7 +216,7 @@ Testing Strategy
Approves plan but requires confirmation for each tool
3. Type your feedback...
Enter to select · ↑/↓ to navigate · Esc to cancel
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
"
`;
@@ -243,6 +243,6 @@ Files to Modify
Approves plan but requires confirmation for each tool
3. Type your feedback...
Enter to select · ↑/↓ to navigate · Esc to cancel
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
"
`;
@@ -2,16 +2,16 @@
exports[`InputPrompt > History Navigation and Completion Suppression > should not render suggestions during history navigation 1`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> second message
> second message
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
"
`;
exports[`InputPrompt > command search (Ctrl+R when not in shell) > expands and collapses long suggestion via Right/Left arrows > command-search-render-collapsed-match 1`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
(r:) Type your message or @path/to/file
(r:) Type your message or @path/to/file
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll →
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll →
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll
...
"
@@ -19,9 +19,9 @@ exports[`InputPrompt > command search (Ctrl+R when not in shell) > expands and c
exports[`InputPrompt > command search (Ctrl+R when not in shell) > expands and collapses long suggestion via Right/Left arrows > command-search-render-expanded-match 1`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
(r:) Type your message or @path/to/file
(r:) Type your message or @path/to/file
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll ←
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll ←
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll
llllllllllllllllllllllllllllllllllllllllllllllllll
"
@@ -29,7 +29,7 @@ exports[`InputPrompt > command search (Ctrl+R when not in shell) > expands and c
exports[`InputPrompt > command search (Ctrl+R when not in shell) > renders match window and expanded view (snapshots) > command-search-render-collapsed-match 1`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
(r:) commit
(r:) commit
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
git commit -m "feat: add search" in src/app
"
@@ -37,7 +37,7 @@ exports[`InputPrompt > command search (Ctrl+R when not in shell) > renders match
exports[`InputPrompt > command search (Ctrl+R when not in shell) > renders match window and expanded view (snapshots) > command-search-render-expanded-match 1`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
(r:) commit
(r:) commit
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
git commit -m "feat: add search" in src/app
"
@@ -45,63 +45,63 @@ exports[`InputPrompt > command search (Ctrl+R when not in shell) > renders match
exports[`InputPrompt > image path transformation snapshots > should snapshot collapsed image path 1`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> [Image ...reenshot2x.png]
> [Image ...reenshot2x.png]
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
"
`;
exports[`InputPrompt > image path transformation snapshots > should snapshot expanded image path when cursor is on it 1`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> @/path/to/screenshots/screenshot2x.png
> @/path/to/screenshots/screenshot2x.png
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
"
`;
exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 1`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> [Pasted Text: 10 lines]
> [Pasted Text: 10 lines]
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
"
`;
exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 2`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> [Pasted Text: 10 lines]
> [Pasted Text: 10 lines]
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
"
`;
exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 3`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> [Pasted Text: 10 lines]
> [Pasted Text: 10 lines]
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
"
`;
exports[`InputPrompt > snapshots > should not show inverted cursor when shell is focused 1`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> Type your message or @path/to/file
> Type your message or @path/to/file
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
"
`;
exports[`InputPrompt > snapshots > should render correctly in shell mode 1`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
! Type your message or @path/to/file
! Type your message or @path/to/file
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
"
`;
exports[`InputPrompt > snapshots > should render correctly in yolo mode 1`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
* Type your message or @path/to/file
* Type your message or @path/to/file
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
"
`;
exports[`InputPrompt > snapshots > should render correctly when accepting edits 1`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> Type your message or @path/to/file
> Type your message or @path/to/file
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
"
`;
@@ -0,0 +1,133 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="751" viewBox="0 0 920 751">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="751" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#3d3f51" textLength="900" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="0" y="19" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="19" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="36" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="36" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> &gt; Settings </text>
<text x="891" y="36" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="891" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="85" width="9" height="17" fill="#ffffff" />
<text x="36" y="87" fill="#000000" textLength="9" lengthAdjust="spacingAndGlyphs">S</text>
<text x="45" y="87" fill="#6c7086" textLength="135" lengthAdjust="spacingAndGlyphs">earch to filter</text>
<text x="873" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="104" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
<text x="891" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="138" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="138" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="138" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="155" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="155" fill="#a6e3a1" textLength="72" lengthAdjust="spacingAndGlyphs">Vim Mode</text>
<text x="828" y="155" fill="#a6e3a1" textLength="45" lengthAdjust="spacingAndGlyphs">false</text>
<text x="891" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="172" fill="#6c7086" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
<text x="891" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="206" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="206" fill="#ffffff" textLength="801" lengthAdjust="spacingAndGlyphs"> Default Approval Mode </text>
<text x="810" y="206" fill="#6c7086" textLength="63" lengthAdjust="spacingAndGlyphs">Default</text>
<text x="891" y="206" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="223" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="223" fill="#6c7086" textLength="738" lengthAdjust="spacingAndGlyphs">The default approval mode for tool execution. &apos;default&apos; prompts for approval, &apos;au…</text>
<text x="891" y="223" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="240" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="240" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="257" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="257" fill="#ffffff" textLength="828" lengthAdjust="spacingAndGlyphs"> Enable Auto Update </text>
<text x="837" y="257" fill="#6c7086" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
<text x="891" y="257" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="274" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="274" fill="#6c7086" textLength="225" lengthAdjust="spacingAndGlyphs">Enable automatic updates.</text>
<text x="891" y="274" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="291" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="291" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="308" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="308" fill="#ffffff" textLength="819" lengthAdjust="spacingAndGlyphs"> Enable Notifications </text>
<text x="828" y="308" fill="#6c7086" textLength="45" lengthAdjust="spacingAndGlyphs">false</text>
<text x="891" y="308" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="325" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="325" fill="#6c7086" textLength="756" lengthAdjust="spacingAndGlyphs">Enable run-event notifications for action-required prompts and session completion. …</text>
<text x="891" y="325" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="342" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="342" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="359" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="359" fill="#ffffff" textLength="783" lengthAdjust="spacingAndGlyphs"> Plan Directory </text>
<text x="792" y="359" fill="#6c7086" textLength="81" lengthAdjust="spacingAndGlyphs">undefined</text>
<text x="891" y="359" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="376" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="376" fill="#6c7086" textLength="720" lengthAdjust="spacingAndGlyphs">The directory where planning artifacts are stored. If not specified, defaults t…</text>
<text x="891" y="376" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="393" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="393" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="410" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="410" fill="#ffffff" textLength="828" lengthAdjust="spacingAndGlyphs"> Plan Model Routing </text>
<text x="837" y="410" fill="#6c7086" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
<text x="891" y="410" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="427" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="427" fill="#6c7086" textLength="765" lengthAdjust="spacingAndGlyphs">Automatically switch between Pro and Flash models based on Plan Mode status. Uses Pr…</text>
<text x="891" y="427" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="444" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="444" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="461" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="461" fill="#ffffff" textLength="846" lengthAdjust="spacingAndGlyphs"> Max Chat Model Attempts </text>
<text x="855" y="461" fill="#6c7086" textLength="18" lengthAdjust="spacingAndGlyphs">10</text>
<text x="891" y="461" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="478" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="478" fill="#6c7086" textLength="729" lengthAdjust="spacingAndGlyphs">Maximum number of attempts for requests to the main chat model. Cannot exceed 10.</text>
<text x="891" y="478" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="495" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="495" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="512" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="512" fill="#ffffff" textLength="819" lengthAdjust="spacingAndGlyphs"> Debug Keystroke Logging </text>
<text x="828" y="512" fill="#6c7086" textLength="45" lengthAdjust="spacingAndGlyphs">false</text>
<text x="891" y="512" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="529" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="529" fill="#6c7086" textLength="450" lengthAdjust="spacingAndGlyphs">Enable debug logging of keystrokes to the console.</text>
<text x="891" y="529" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="546" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="546" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="563" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="563" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="563" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="580" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="580" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="597" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="597" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Apply To </text>
<text x="891" y="597" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="614" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="614" fill="#a6e3a1" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
<text x="891" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="631" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="631" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Workspace Settings </text>
<text x="891" y="631" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="648" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="648" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> System Settings </text>
<text x="891" y="648" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="665" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="665" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="682" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="682" fill="#6c7086" textLength="657" lengthAdjust="spacingAndGlyphs">(Use Enter to select, Ctrl+L to reset, Tab to change focus, Esc to close)</text>
<text x="891" y="682" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="699" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="699" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="716" fill="#3d3f51" textLength="900" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────────╯</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

@@ -0,0 +1,133 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="751" viewBox="0 0 920 751">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="751" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#3d3f51" textLength="900" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="0" y="19" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="19" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="36" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="36" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> &gt; Settings </text>
<text x="891" y="36" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="891" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="85" width="9" height="17" fill="#ffffff" />
<text x="36" y="87" fill="#000000" textLength="9" lengthAdjust="spacingAndGlyphs">S</text>
<text x="45" y="87" fill="#6c7086" textLength="135" lengthAdjust="spacingAndGlyphs">earch to filter</text>
<text x="873" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="104" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
<text x="891" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="138" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="138" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="138" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="155" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="155" fill="#a6e3a1" textLength="72" lengthAdjust="spacingAndGlyphs">Vim Mode</text>
<text x="828" y="155" fill="#a6e3a1" textLength="45" lengthAdjust="spacingAndGlyphs">true*</text>
<text x="891" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="172" fill="#6c7086" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
<text x="891" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="206" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="206" fill="#ffffff" textLength="801" lengthAdjust="spacingAndGlyphs"> Default Approval Mode </text>
<text x="810" y="206" fill="#6c7086" textLength="63" lengthAdjust="spacingAndGlyphs">Default</text>
<text x="891" y="206" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="223" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="223" fill="#6c7086" textLength="738" lengthAdjust="spacingAndGlyphs">The default approval mode for tool execution. &apos;default&apos; prompts for approval, &apos;au…</text>
<text x="891" y="223" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="240" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="240" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="257" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="257" fill="#ffffff" textLength="828" lengthAdjust="spacingAndGlyphs"> Enable Auto Update </text>
<text x="837" y="257" fill="#6c7086" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
<text x="891" y="257" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="274" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="274" fill="#6c7086" textLength="225" lengthAdjust="spacingAndGlyphs">Enable automatic updates.</text>
<text x="891" y="274" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="291" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="291" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="308" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="308" fill="#ffffff" textLength="819" lengthAdjust="spacingAndGlyphs"> Enable Notifications </text>
<text x="828" y="308" fill="#6c7086" textLength="45" lengthAdjust="spacingAndGlyphs">false</text>
<text x="891" y="308" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="325" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="325" fill="#6c7086" textLength="756" lengthAdjust="spacingAndGlyphs">Enable run-event notifications for action-required prompts and session completion. …</text>
<text x="891" y="325" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="342" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="342" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="359" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="359" fill="#ffffff" textLength="783" lengthAdjust="spacingAndGlyphs"> Plan Directory </text>
<text x="792" y="359" fill="#6c7086" textLength="81" lengthAdjust="spacingAndGlyphs">undefined</text>
<text x="891" y="359" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="376" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="376" fill="#6c7086" textLength="720" lengthAdjust="spacingAndGlyphs">The directory where planning artifacts are stored. If not specified, defaults t…</text>
<text x="891" y="376" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="393" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="393" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="410" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="410" fill="#ffffff" textLength="828" lengthAdjust="spacingAndGlyphs"> Plan Model Routing </text>
<text x="837" y="410" fill="#6c7086" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
<text x="891" y="410" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="427" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="427" fill="#6c7086" textLength="765" lengthAdjust="spacingAndGlyphs">Automatically switch between Pro and Flash models based on Plan Mode status. Uses Pr…</text>
<text x="891" y="427" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="444" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="444" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="461" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="461" fill="#ffffff" textLength="846" lengthAdjust="spacingAndGlyphs"> Max Chat Model Attempts </text>
<text x="855" y="461" fill="#6c7086" textLength="18" lengthAdjust="spacingAndGlyphs">10</text>
<text x="891" y="461" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="478" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="478" fill="#6c7086" textLength="729" lengthAdjust="spacingAndGlyphs">Maximum number of attempts for requests to the main chat model. Cannot exceed 10.</text>
<text x="891" y="478" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="495" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="495" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="512" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="512" fill="#ffffff" textLength="819" lengthAdjust="spacingAndGlyphs"> Debug Keystroke Logging </text>
<text x="828" y="512" fill="#6c7086" textLength="45" lengthAdjust="spacingAndGlyphs">false</text>
<text x="891" y="512" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="529" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="529" fill="#6c7086" textLength="450" lengthAdjust="spacingAndGlyphs">Enable debug logging of keystrokes to the console.</text>
<text x="891" y="529" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="546" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="546" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="563" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="563" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="563" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="580" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="580" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="597" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="597" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Apply To </text>
<text x="891" y="597" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="614" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="614" fill="#a6e3a1" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
<text x="891" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="631" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="631" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Workspace Settings </text>
<text x="891" y="631" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="648" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="648" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> System Settings </text>
<text x="891" y="648" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="665" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="665" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="682" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="682" fill="#6c7086" textLength="657" lengthAdjust="spacingAndGlyphs">(Use Enter to select, Ctrl+L to reset, Tab to change focus, Esc to close)</text>
<text x="891" y="682" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="699" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="699" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="716" fill="#3d3f51" textLength="900" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────────╯</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

@@ -0,0 +1,131 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="751" viewBox="0 0 920 751">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="751" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#3d3f51" textLength="900" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="0" y="19" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="19" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="36" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="36" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> &gt; Settings </text>
<text x="891" y="36" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="891" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="85" width="9" height="17" fill="#ffffff" />
<text x="36" y="87" fill="#000000" textLength="9" lengthAdjust="spacingAndGlyphs">S</text>
<text x="45" y="87" fill="#6c7086" textLength="135" lengthAdjust="spacingAndGlyphs">earch to filter</text>
<text x="873" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="104" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
<text x="891" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="138" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="138" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="138" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="155" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="155" fill="#a6e3a1" textLength="72" lengthAdjust="spacingAndGlyphs">Vim Mode</text>
<text x="819" y="155" fill="#a6e3a1" textLength="54" lengthAdjust="spacingAndGlyphs">false*</text>
<text x="891" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="172" fill="#6c7086" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
<text x="891" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="206" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="206" fill="#ffffff" textLength="801" lengthAdjust="spacingAndGlyphs"> Default Approval Mode </text>
<text x="810" y="206" fill="#6c7086" textLength="63" lengthAdjust="spacingAndGlyphs">Default</text>
<text x="891" y="206" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="223" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="223" fill="#6c7086" textLength="738" lengthAdjust="spacingAndGlyphs">The default approval mode for tool execution. &apos;default&apos; prompts for approval, &apos;au…</text>
<text x="891" y="223" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="240" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="240" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="257" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="257" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Enable Auto Update true* </text>
<text x="891" y="257" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="274" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="274" fill="#6c7086" textLength="225" lengthAdjust="spacingAndGlyphs">Enable automatic updates.</text>
<text x="891" y="274" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="291" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="291" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="308" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="308" fill="#ffffff" textLength="819" lengthAdjust="spacingAndGlyphs"> Enable Notifications </text>
<text x="828" y="308" fill="#6c7086" textLength="45" lengthAdjust="spacingAndGlyphs">false</text>
<text x="891" y="308" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="325" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="325" fill="#6c7086" textLength="756" lengthAdjust="spacingAndGlyphs">Enable run-event notifications for action-required prompts and session completion. …</text>
<text x="891" y="325" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="342" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="342" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="359" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="359" fill="#ffffff" textLength="783" lengthAdjust="spacingAndGlyphs"> Plan Directory </text>
<text x="792" y="359" fill="#6c7086" textLength="81" lengthAdjust="spacingAndGlyphs">undefined</text>
<text x="891" y="359" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="376" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="376" fill="#6c7086" textLength="720" lengthAdjust="spacingAndGlyphs">The directory where planning artifacts are stored. If not specified, defaults t…</text>
<text x="891" y="376" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="393" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="393" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="410" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="410" fill="#ffffff" textLength="828" lengthAdjust="spacingAndGlyphs"> Plan Model Routing </text>
<text x="837" y="410" fill="#6c7086" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
<text x="891" y="410" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="427" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="427" fill="#6c7086" textLength="765" lengthAdjust="spacingAndGlyphs">Automatically switch between Pro and Flash models based on Plan Mode status. Uses Pr…</text>
<text x="891" y="427" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="444" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="444" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="461" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="461" fill="#ffffff" textLength="846" lengthAdjust="spacingAndGlyphs"> Max Chat Model Attempts </text>
<text x="855" y="461" fill="#6c7086" textLength="18" lengthAdjust="spacingAndGlyphs">10</text>
<text x="891" y="461" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="478" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="478" fill="#6c7086" textLength="729" lengthAdjust="spacingAndGlyphs">Maximum number of attempts for requests to the main chat model. Cannot exceed 10.</text>
<text x="891" y="478" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="495" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="495" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="512" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="512" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Debug Keystroke Logging false* </text>
<text x="891" y="512" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="529" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="529" fill="#6c7086" textLength="450" lengthAdjust="spacingAndGlyphs">Enable debug logging of keystrokes to the console.</text>
<text x="891" y="529" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="546" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="546" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="563" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="563" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="563" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="580" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="580" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="597" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="597" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Apply To </text>
<text x="891" y="597" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="614" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="614" fill="#a6e3a1" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
<text x="891" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="631" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="631" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Workspace Settings </text>
<text x="891" y="631" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="648" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="648" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> System Settings </text>
<text x="891" y="648" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="665" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="665" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="682" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="682" fill="#6c7086" textLength="657" lengthAdjust="spacingAndGlyphs">(Use Enter to select, Ctrl+L to reset, Tab to change focus, Esc to close)</text>
<text x="891" y="682" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="699" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="699" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="716" fill="#3d3f51" textLength="900" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────────╯</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

@@ -0,0 +1,133 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="751" viewBox="0 0 920 751">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="751" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#3d3f51" textLength="900" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="0" y="19" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="19" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="36" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="36" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> &gt; Settings </text>
<text x="891" y="36" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="891" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="85" width="9" height="17" fill="#ffffff" />
<text x="36" y="87" fill="#000000" textLength="9" lengthAdjust="spacingAndGlyphs">S</text>
<text x="45" y="87" fill="#6c7086" textLength="135" lengthAdjust="spacingAndGlyphs">earch to filter</text>
<text x="873" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="104" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
<text x="891" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="138" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="138" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="138" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="155" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="155" fill="#a6e3a1" textLength="72" lengthAdjust="spacingAndGlyphs">Vim Mode</text>
<text x="828" y="155" fill="#a6e3a1" textLength="45" lengthAdjust="spacingAndGlyphs">false</text>
<text x="891" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="172" fill="#6c7086" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
<text x="891" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="206" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="206" fill="#ffffff" textLength="801" lengthAdjust="spacingAndGlyphs"> Default Approval Mode </text>
<text x="810" y="206" fill="#6c7086" textLength="63" lengthAdjust="spacingAndGlyphs">Default</text>
<text x="891" y="206" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="223" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="223" fill="#6c7086" textLength="738" lengthAdjust="spacingAndGlyphs">The default approval mode for tool execution. &apos;default&apos; prompts for approval, &apos;au…</text>
<text x="891" y="223" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="240" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="240" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="257" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="257" fill="#ffffff" textLength="828" lengthAdjust="spacingAndGlyphs"> Enable Auto Update </text>
<text x="837" y="257" fill="#6c7086" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
<text x="891" y="257" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="274" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="274" fill="#6c7086" textLength="225" lengthAdjust="spacingAndGlyphs">Enable automatic updates.</text>
<text x="891" y="274" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="291" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="291" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="308" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="308" fill="#ffffff" textLength="819" lengthAdjust="spacingAndGlyphs"> Enable Notifications </text>
<text x="828" y="308" fill="#6c7086" textLength="45" lengthAdjust="spacingAndGlyphs">false</text>
<text x="891" y="308" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="325" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="325" fill="#6c7086" textLength="756" lengthAdjust="spacingAndGlyphs">Enable run-event notifications for action-required prompts and session completion. …</text>
<text x="891" y="325" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="342" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="342" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="359" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="359" fill="#ffffff" textLength="783" lengthAdjust="spacingAndGlyphs"> Plan Directory </text>
<text x="792" y="359" fill="#6c7086" textLength="81" lengthAdjust="spacingAndGlyphs">undefined</text>
<text x="891" y="359" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="376" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="376" fill="#6c7086" textLength="720" lengthAdjust="spacingAndGlyphs">The directory where planning artifacts are stored. If not specified, defaults t…</text>
<text x="891" y="376" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="393" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="393" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="410" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="410" fill="#ffffff" textLength="828" lengthAdjust="spacingAndGlyphs"> Plan Model Routing </text>
<text x="837" y="410" fill="#6c7086" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
<text x="891" y="410" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="427" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="427" fill="#6c7086" textLength="765" lengthAdjust="spacingAndGlyphs">Automatically switch between Pro and Flash models based on Plan Mode status. Uses Pr…</text>
<text x="891" y="427" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="444" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="444" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="461" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="461" fill="#ffffff" textLength="846" lengthAdjust="spacingAndGlyphs"> Max Chat Model Attempts </text>
<text x="855" y="461" fill="#6c7086" textLength="18" lengthAdjust="spacingAndGlyphs">10</text>
<text x="891" y="461" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="478" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="478" fill="#6c7086" textLength="729" lengthAdjust="spacingAndGlyphs">Maximum number of attempts for requests to the main chat model. Cannot exceed 10.</text>
<text x="891" y="478" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="495" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="495" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="512" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="512" fill="#ffffff" textLength="819" lengthAdjust="spacingAndGlyphs"> Debug Keystroke Logging </text>
<text x="828" y="512" fill="#6c7086" textLength="45" lengthAdjust="spacingAndGlyphs">false</text>
<text x="891" y="512" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="529" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="529" fill="#6c7086" textLength="450" lengthAdjust="spacingAndGlyphs">Enable debug logging of keystrokes to the console.</text>
<text x="891" y="529" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="546" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="546" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="563" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="563" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="563" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="580" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="580" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="597" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="597" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Apply To </text>
<text x="891" y="597" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="614" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="614" fill="#a6e3a1" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
<text x="891" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="631" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="631" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Workspace Settings </text>
<text x="891" y="631" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="648" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="648" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> System Settings </text>
<text x="891" y="648" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="665" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="665" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="682" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="682" fill="#6c7086" textLength="657" lengthAdjust="spacingAndGlyphs">(Use Enter to select, Ctrl+L to reset, Tab to change focus, Esc to close)</text>
<text x="891" y="682" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="699" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="699" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="716" fill="#3d3f51" textLength="900" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────────╯</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

@@ -0,0 +1,133 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="751" viewBox="0 0 920 751">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="751" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#3d3f51" textLength="900" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="0" y="19" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="19" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="36" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="36" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> &gt; Settings </text>
<text x="891" y="36" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="891" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="85" width="9" height="17" fill="#ffffff" />
<text x="36" y="87" fill="#000000" textLength="9" lengthAdjust="spacingAndGlyphs">S</text>
<text x="45" y="87" fill="#6c7086" textLength="135" lengthAdjust="spacingAndGlyphs">earch to filter</text>
<text x="873" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="104" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
<text x="891" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="138" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="138" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="138" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="155" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="155" fill="#a6e3a1" textLength="72" lengthAdjust="spacingAndGlyphs">Vim Mode</text>
<text x="828" y="155" fill="#a6e3a1" textLength="45" lengthAdjust="spacingAndGlyphs">false</text>
<text x="891" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="172" fill="#6c7086" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
<text x="891" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="206" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="206" fill="#ffffff" textLength="801" lengthAdjust="spacingAndGlyphs"> Default Approval Mode </text>
<text x="810" y="206" fill="#6c7086" textLength="63" lengthAdjust="spacingAndGlyphs">Default</text>
<text x="891" y="206" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="223" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="223" fill="#6c7086" textLength="738" lengthAdjust="spacingAndGlyphs">The default approval mode for tool execution. &apos;default&apos; prompts for approval, &apos;au…</text>
<text x="891" y="223" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="240" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="240" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="257" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="257" fill="#ffffff" textLength="828" lengthAdjust="spacingAndGlyphs"> Enable Auto Update </text>
<text x="837" y="257" fill="#6c7086" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
<text x="891" y="257" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="274" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="274" fill="#6c7086" textLength="225" lengthAdjust="spacingAndGlyphs">Enable automatic updates.</text>
<text x="891" y="274" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="291" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="291" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="308" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="308" fill="#ffffff" textLength="819" lengthAdjust="spacingAndGlyphs"> Enable Notifications </text>
<text x="828" y="308" fill="#6c7086" textLength="45" lengthAdjust="spacingAndGlyphs">false</text>
<text x="891" y="308" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="325" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="325" fill="#6c7086" textLength="756" lengthAdjust="spacingAndGlyphs">Enable run-event notifications for action-required prompts and session completion. …</text>
<text x="891" y="325" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="342" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="342" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="359" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="359" fill="#ffffff" textLength="783" lengthAdjust="spacingAndGlyphs"> Plan Directory </text>
<text x="792" y="359" fill="#6c7086" textLength="81" lengthAdjust="spacingAndGlyphs">undefined</text>
<text x="891" y="359" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="376" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="376" fill="#6c7086" textLength="720" lengthAdjust="spacingAndGlyphs">The directory where planning artifacts are stored. If not specified, defaults t…</text>
<text x="891" y="376" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="393" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="393" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="410" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="410" fill="#ffffff" textLength="828" lengthAdjust="spacingAndGlyphs"> Plan Model Routing </text>
<text x="837" y="410" fill="#6c7086" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
<text x="891" y="410" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="427" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="427" fill="#6c7086" textLength="765" lengthAdjust="spacingAndGlyphs">Automatically switch between Pro and Flash models based on Plan Mode status. Uses Pr…</text>
<text x="891" y="427" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="444" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="444" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="461" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="461" fill="#ffffff" textLength="846" lengthAdjust="spacingAndGlyphs"> Max Chat Model Attempts </text>
<text x="855" y="461" fill="#6c7086" textLength="18" lengthAdjust="spacingAndGlyphs">10</text>
<text x="891" y="461" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="478" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="478" fill="#6c7086" textLength="729" lengthAdjust="spacingAndGlyphs">Maximum number of attempts for requests to the main chat model. Cannot exceed 10.</text>
<text x="891" y="478" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="495" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="495" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="512" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="512" fill="#ffffff" textLength="819" lengthAdjust="spacingAndGlyphs"> Debug Keystroke Logging </text>
<text x="828" y="512" fill="#6c7086" textLength="45" lengthAdjust="spacingAndGlyphs">false</text>
<text x="891" y="512" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="529" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="529" fill="#6c7086" textLength="450" lengthAdjust="spacingAndGlyphs">Enable debug logging of keystrokes to the console.</text>
<text x="891" y="529" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="546" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="546" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="563" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="563" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="563" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="580" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="580" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="597" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="597" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Apply To </text>
<text x="891" y="597" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="614" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="614" fill="#a6e3a1" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
<text x="891" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="631" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="631" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Workspace Settings </text>
<text x="891" y="631" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="648" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="648" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> System Settings </text>
<text x="891" y="648" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="665" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="665" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="682" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="682" fill="#6c7086" textLength="657" lengthAdjust="spacingAndGlyphs">(Use Enter to select, Ctrl+L to reset, Tab to change focus, Esc to close)</text>
<text x="891" y="682" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="699" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="699" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="716" fill="#3d3f51" textLength="900" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────────╯</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

@@ -0,0 +1,131 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="751" viewBox="0 0 920 751">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="751" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#3d3f51" textLength="900" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="0" y="19" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="19" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="36" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="36" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Settings </text>
<text x="891" y="36" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#3d3f51" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="891" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="36" y="87" fill="#6c7086" textLength="144" lengthAdjust="spacingAndGlyphs">Search to filter</text>
<text x="873" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="104" fill="#3d3f51" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
<text x="891" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="138" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="138" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="138" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="155" fill="#ffffff" textLength="819" lengthAdjust="spacingAndGlyphs"> Vim Mode </text>
<text x="828" y="155" fill="#6c7086" textLength="45" lengthAdjust="spacingAndGlyphs">false</text>
<text x="891" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="172" fill="#6c7086" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
<text x="891" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="206" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="206" fill="#ffffff" textLength="801" lengthAdjust="spacingAndGlyphs"> Default Approval Mode </text>
<text x="810" y="206" fill="#6c7086" textLength="63" lengthAdjust="spacingAndGlyphs">Default</text>
<text x="891" y="206" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="223" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="223" fill="#6c7086" textLength="738" lengthAdjust="spacingAndGlyphs">The default approval mode for tool execution. &apos;default&apos; prompts for approval, &apos;au…</text>
<text x="891" y="223" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="240" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="240" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="257" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="257" fill="#ffffff" textLength="828" lengthAdjust="spacingAndGlyphs"> Enable Auto Update </text>
<text x="837" y="257" fill="#6c7086" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
<text x="891" y="257" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="274" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="274" fill="#6c7086" textLength="225" lengthAdjust="spacingAndGlyphs">Enable automatic updates.</text>
<text x="891" y="274" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="291" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="291" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="308" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="308" fill="#ffffff" textLength="819" lengthAdjust="spacingAndGlyphs"> Enable Notifications </text>
<text x="828" y="308" fill="#6c7086" textLength="45" lengthAdjust="spacingAndGlyphs">false</text>
<text x="891" y="308" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="325" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="325" fill="#6c7086" textLength="756" lengthAdjust="spacingAndGlyphs">Enable run-event notifications for action-required prompts and session completion. …</text>
<text x="891" y="325" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="342" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="342" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="359" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="359" fill="#ffffff" textLength="783" lengthAdjust="spacingAndGlyphs"> Plan Directory </text>
<text x="792" y="359" fill="#6c7086" textLength="81" lengthAdjust="spacingAndGlyphs">undefined</text>
<text x="891" y="359" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="376" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="376" fill="#6c7086" textLength="720" lengthAdjust="spacingAndGlyphs">The directory where planning artifacts are stored. If not specified, defaults t…</text>
<text x="891" y="376" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="393" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="393" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="410" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="410" fill="#ffffff" textLength="828" lengthAdjust="spacingAndGlyphs"> Plan Model Routing </text>
<text x="837" y="410" fill="#6c7086" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
<text x="891" y="410" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="427" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="427" fill="#6c7086" textLength="765" lengthAdjust="spacingAndGlyphs">Automatically switch between Pro and Flash models based on Plan Mode status. Uses Pr…</text>
<text x="891" y="427" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="444" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="444" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="461" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="461" fill="#ffffff" textLength="846" lengthAdjust="spacingAndGlyphs"> Max Chat Model Attempts </text>
<text x="855" y="461" fill="#6c7086" textLength="18" lengthAdjust="spacingAndGlyphs">10</text>
<text x="891" y="461" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="478" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="478" fill="#6c7086" textLength="729" lengthAdjust="spacingAndGlyphs">Maximum number of attempts for requests to the main chat model. Cannot exceed 10.</text>
<text x="891" y="478" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="495" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="495" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="512" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="512" fill="#ffffff" textLength="819" lengthAdjust="spacingAndGlyphs"> Debug Keystroke Logging </text>
<text x="828" y="512" fill="#6c7086" textLength="45" lengthAdjust="spacingAndGlyphs">false</text>
<text x="891" y="512" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="529" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="529" fill="#6c7086" textLength="450" lengthAdjust="spacingAndGlyphs">Enable debug logging of keystrokes to the console.</text>
<text x="891" y="529" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="546" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="546" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="563" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="563" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="563" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="580" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="580" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="597" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="597" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> &gt; Apply To </text>
<text x="891" y="597" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="614" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="614" fill="#a6e3a1" textLength="18" lengthAdjust="spacingAndGlyphs">1.</text>
<text x="72" y="614" fill="#a6e3a1" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
<text x="891" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="631" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="631" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> 2. Workspace Settings </text>
<text x="891" y="631" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="648" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="648" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> 3. System Settings </text>
<text x="891" y="648" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="665" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="665" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="682" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="682" fill="#6c7086" textLength="657" lengthAdjust="spacingAndGlyphs">(Use Enter to select, Ctrl+L to reset, Tab to change focus, Esc to close)</text>
<text x="891" y="682" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="699" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="699" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="716" fill="#3d3f51" textLength="900" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────────╯</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

@@ -0,0 +1,132 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="751" viewBox="0 0 920 751">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="751" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#3d3f51" textLength="900" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="0" y="19" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="19" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="36" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="36" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> &gt; Settings </text>
<text x="891" y="36" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="891" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="85" width="9" height="17" fill="#ffffff" />
<text x="36" y="87" fill="#000000" textLength="9" lengthAdjust="spacingAndGlyphs">S</text>
<text x="45" y="87" fill="#6c7086" textLength="135" lengthAdjust="spacingAndGlyphs">earch to filter</text>
<text x="873" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="104" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
<text x="891" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="138" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="138" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="138" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="155" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="155" fill="#a6e3a1" textLength="72" lengthAdjust="spacingAndGlyphs">Vim Mode</text>
<text x="819" y="155" fill="#a6e3a1" textLength="54" lengthAdjust="spacingAndGlyphs">false*</text>
<text x="891" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="172" fill="#6c7086" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
<text x="891" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="206" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="206" fill="#ffffff" textLength="801" lengthAdjust="spacingAndGlyphs"> Default Approval Mode </text>
<text x="810" y="206" fill="#6c7086" textLength="63" lengthAdjust="spacingAndGlyphs">Default</text>
<text x="891" y="206" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="223" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="223" fill="#6c7086" textLength="738" lengthAdjust="spacingAndGlyphs">The default approval mode for tool execution. &apos;default&apos; prompts for approval, &apos;au…</text>
<text x="891" y="223" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="240" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="240" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="257" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="257" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Enable Auto Update false* </text>
<text x="891" y="257" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="274" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="274" fill="#6c7086" textLength="225" lengthAdjust="spacingAndGlyphs">Enable automatic updates.</text>
<text x="891" y="274" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="291" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="291" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="308" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="308" fill="#ffffff" textLength="819" lengthAdjust="spacingAndGlyphs"> Enable Notifications </text>
<text x="828" y="308" fill="#6c7086" textLength="45" lengthAdjust="spacingAndGlyphs">false</text>
<text x="891" y="308" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="325" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="325" fill="#6c7086" textLength="756" lengthAdjust="spacingAndGlyphs">Enable run-event notifications for action-required prompts and session completion. …</text>
<text x="891" y="325" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="342" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="342" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="359" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="359" fill="#ffffff" textLength="783" lengthAdjust="spacingAndGlyphs"> Plan Directory </text>
<text x="792" y="359" fill="#6c7086" textLength="81" lengthAdjust="spacingAndGlyphs">undefined</text>
<text x="891" y="359" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="376" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="376" fill="#6c7086" textLength="720" lengthAdjust="spacingAndGlyphs">The directory where planning artifacts are stored. If not specified, defaults t…</text>
<text x="891" y="376" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="393" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="393" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="410" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="410" fill="#ffffff" textLength="828" lengthAdjust="spacingAndGlyphs"> Plan Model Routing </text>
<text x="837" y="410" fill="#6c7086" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
<text x="891" y="410" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="427" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="427" fill="#6c7086" textLength="765" lengthAdjust="spacingAndGlyphs">Automatically switch between Pro and Flash models based on Plan Mode status. Uses Pr…</text>
<text x="891" y="427" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="444" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="444" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="461" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="461" fill="#ffffff" textLength="846" lengthAdjust="spacingAndGlyphs"> Max Chat Model Attempts </text>
<text x="855" y="461" fill="#6c7086" textLength="18" lengthAdjust="spacingAndGlyphs">10</text>
<text x="891" y="461" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="478" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="478" fill="#6c7086" textLength="729" lengthAdjust="spacingAndGlyphs">Maximum number of attempts for requests to the main chat model. Cannot exceed 10.</text>
<text x="891" y="478" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="495" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="495" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="512" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="512" fill="#ffffff" textLength="819" lengthAdjust="spacingAndGlyphs"> Debug Keystroke Logging </text>
<text x="828" y="512" fill="#6c7086" textLength="45" lengthAdjust="spacingAndGlyphs">false</text>
<text x="891" y="512" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="529" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="529" fill="#6c7086" textLength="450" lengthAdjust="spacingAndGlyphs">Enable debug logging of keystrokes to the console.</text>
<text x="891" y="529" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="546" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="546" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="563" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="563" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="563" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="580" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="580" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="597" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="597" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Apply To </text>
<text x="891" y="597" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="614" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="614" fill="#a6e3a1" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
<text x="891" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="631" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="631" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Workspace Settings </text>
<text x="891" y="631" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="648" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="648" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> System Settings </text>
<text x="891" y="648" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="665" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="665" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="682" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="682" fill="#6c7086" textLength="657" lengthAdjust="spacingAndGlyphs">(Use Enter to select, Ctrl+L to reset, Tab to change focus, Esc to close)</text>
<text x="891" y="682" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="699" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="699" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="716" fill="#3d3f51" textLength="900" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────────╯</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

@@ -0,0 +1,133 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="751" viewBox="0 0 920 751">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="751" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#3d3f51" textLength="900" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="0" y="19" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="19" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="36" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="36" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> &gt; Settings </text>
<text x="891" y="36" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="891" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="85" width="9" height="17" fill="#ffffff" />
<text x="36" y="87" fill="#000000" textLength="9" lengthAdjust="spacingAndGlyphs">S</text>
<text x="45" y="87" fill="#6c7086" textLength="135" lengthAdjust="spacingAndGlyphs">earch to filter</text>
<text x="873" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="104" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
<text x="891" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="138" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="138" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="138" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="155" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="155" fill="#a6e3a1" textLength="72" lengthAdjust="spacingAndGlyphs">Vim Mode</text>
<text x="828" y="155" fill="#a6e3a1" textLength="45" lengthAdjust="spacingAndGlyphs">false</text>
<text x="891" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="172" fill="#6c7086" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
<text x="891" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="206" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="206" fill="#ffffff" textLength="801" lengthAdjust="spacingAndGlyphs"> Default Approval Mode </text>
<text x="810" y="206" fill="#6c7086" textLength="63" lengthAdjust="spacingAndGlyphs">Default</text>
<text x="891" y="206" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="223" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="223" fill="#6c7086" textLength="738" lengthAdjust="spacingAndGlyphs">The default approval mode for tool execution. &apos;default&apos; prompts for approval, &apos;au…</text>
<text x="891" y="223" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="240" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="240" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="257" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="257" fill="#ffffff" textLength="828" lengthAdjust="spacingAndGlyphs"> Enable Auto Update </text>
<text x="837" y="257" fill="#6c7086" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
<text x="891" y="257" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="274" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="274" fill="#6c7086" textLength="225" lengthAdjust="spacingAndGlyphs">Enable automatic updates.</text>
<text x="891" y="274" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="291" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="291" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="308" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="308" fill="#ffffff" textLength="819" lengthAdjust="spacingAndGlyphs"> Enable Notifications </text>
<text x="828" y="308" fill="#6c7086" textLength="45" lengthAdjust="spacingAndGlyphs">false</text>
<text x="891" y="308" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="325" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="325" fill="#6c7086" textLength="756" lengthAdjust="spacingAndGlyphs">Enable run-event notifications for action-required prompts and session completion. …</text>
<text x="891" y="325" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="342" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="342" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="359" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="359" fill="#ffffff" textLength="783" lengthAdjust="spacingAndGlyphs"> Plan Directory </text>
<text x="792" y="359" fill="#6c7086" textLength="81" lengthAdjust="spacingAndGlyphs">undefined</text>
<text x="891" y="359" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="376" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="376" fill="#6c7086" textLength="720" lengthAdjust="spacingAndGlyphs">The directory where planning artifacts are stored. If not specified, defaults t…</text>
<text x="891" y="376" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="393" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="393" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="410" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="410" fill="#ffffff" textLength="828" lengthAdjust="spacingAndGlyphs"> Plan Model Routing </text>
<text x="837" y="410" fill="#6c7086" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
<text x="891" y="410" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="427" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="427" fill="#6c7086" textLength="765" lengthAdjust="spacingAndGlyphs">Automatically switch between Pro and Flash models based on Plan Mode status. Uses Pr…</text>
<text x="891" y="427" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="444" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="444" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="461" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="461" fill="#ffffff" textLength="846" lengthAdjust="spacingAndGlyphs"> Max Chat Model Attempts </text>
<text x="855" y="461" fill="#6c7086" textLength="18" lengthAdjust="spacingAndGlyphs">10</text>
<text x="891" y="461" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="478" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="478" fill="#6c7086" textLength="729" lengthAdjust="spacingAndGlyphs">Maximum number of attempts for requests to the main chat model. Cannot exceed 10.</text>
<text x="891" y="478" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="495" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="495" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="512" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="512" fill="#ffffff" textLength="819" lengthAdjust="spacingAndGlyphs"> Debug Keystroke Logging </text>
<text x="828" y="512" fill="#6c7086" textLength="45" lengthAdjust="spacingAndGlyphs">false</text>
<text x="891" y="512" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="529" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="529" fill="#6c7086" textLength="450" lengthAdjust="spacingAndGlyphs">Enable debug logging of keystrokes to the console.</text>
<text x="891" y="529" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="546" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="546" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="563" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="563" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="563" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="580" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="580" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="597" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="597" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Apply To </text>
<text x="891" y="597" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="614" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="614" fill="#a6e3a1" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
<text x="891" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="631" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="631" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Workspace Settings </text>
<text x="891" y="631" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="648" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="648" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> System Settings </text>
<text x="891" y="648" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="665" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="665" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="682" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="682" fill="#6c7086" textLength="657" lengthAdjust="spacingAndGlyphs">(Use Enter to select, Ctrl+L to reset, Tab to change focus, Esc to close)</text>
<text x="891" y="682" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="699" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="699" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="716" fill="#3d3f51" textLength="900" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────────╯</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

@@ -0,0 +1,131 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="751" viewBox="0 0 920 751">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="751" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#3d3f51" textLength="900" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="0" y="19" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="19" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="36" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="36" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> &gt; Settings </text>
<text x="891" y="36" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="891" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="85" width="9" height="17" fill="#ffffff" />
<text x="36" y="87" fill="#000000" textLength="9" lengthAdjust="spacingAndGlyphs">S</text>
<text x="45" y="87" fill="#6c7086" textLength="135" lengthAdjust="spacingAndGlyphs">earch to filter</text>
<text x="873" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="104" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
<text x="891" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="138" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="138" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="138" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="155" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="155" fill="#a6e3a1" textLength="72" lengthAdjust="spacingAndGlyphs">Vim Mode</text>
<text x="828" y="155" fill="#a6e3a1" textLength="45" lengthAdjust="spacingAndGlyphs">true*</text>
<text x="891" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="172" fill="#6c7086" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
<text x="891" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="206" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="206" fill="#ffffff" textLength="801" lengthAdjust="spacingAndGlyphs"> Default Approval Mode </text>
<text x="810" y="206" fill="#6c7086" textLength="63" lengthAdjust="spacingAndGlyphs">Default</text>
<text x="891" y="206" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="223" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="223" fill="#6c7086" textLength="738" lengthAdjust="spacingAndGlyphs">The default approval mode for tool execution. &apos;default&apos; prompts for approval, &apos;au…</text>
<text x="891" y="223" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="240" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="240" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="257" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="257" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Enable Auto Update false* </text>
<text x="891" y="257" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="274" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="274" fill="#6c7086" textLength="225" lengthAdjust="spacingAndGlyphs">Enable automatic updates.</text>
<text x="891" y="274" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="291" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="291" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="308" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="308" fill="#ffffff" textLength="819" lengthAdjust="spacingAndGlyphs"> Enable Notifications </text>
<text x="828" y="308" fill="#6c7086" textLength="45" lengthAdjust="spacingAndGlyphs">false</text>
<text x="891" y="308" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="325" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="325" fill="#6c7086" textLength="756" lengthAdjust="spacingAndGlyphs">Enable run-event notifications for action-required prompts and session completion. …</text>
<text x="891" y="325" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="342" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="342" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="359" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="359" fill="#ffffff" textLength="783" lengthAdjust="spacingAndGlyphs"> Plan Directory </text>
<text x="792" y="359" fill="#6c7086" textLength="81" lengthAdjust="spacingAndGlyphs">undefined</text>
<text x="891" y="359" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="376" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="376" fill="#6c7086" textLength="720" lengthAdjust="spacingAndGlyphs">The directory where planning artifacts are stored. If not specified, defaults t…</text>
<text x="891" y="376" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="393" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="393" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="410" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="410" fill="#ffffff" textLength="828" lengthAdjust="spacingAndGlyphs"> Plan Model Routing </text>
<text x="837" y="410" fill="#6c7086" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
<text x="891" y="410" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="427" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="427" fill="#6c7086" textLength="765" lengthAdjust="spacingAndGlyphs">Automatically switch between Pro and Flash models based on Plan Mode status. Uses Pr…</text>
<text x="891" y="427" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="444" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="444" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="461" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="461" fill="#ffffff" textLength="846" lengthAdjust="spacingAndGlyphs"> Max Chat Model Attempts </text>
<text x="855" y="461" fill="#6c7086" textLength="18" lengthAdjust="spacingAndGlyphs">10</text>
<text x="891" y="461" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="478" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="478" fill="#6c7086" textLength="729" lengthAdjust="spacingAndGlyphs">Maximum number of attempts for requests to the main chat model. Cannot exceed 10.</text>
<text x="891" y="478" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="495" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="495" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="512" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="512" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Debug Keystroke Logging true* </text>
<text x="891" y="512" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="529" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="529" fill="#6c7086" textLength="450" lengthAdjust="spacingAndGlyphs">Enable debug logging of keystrokes to the console.</text>
<text x="891" y="529" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="546" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="546" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="563" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="563" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="563" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="580" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="580" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="597" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="597" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Apply To </text>
<text x="891" y="597" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="614" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="614" fill="#a6e3a1" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
<text x="891" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="631" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="631" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Workspace Settings </text>
<text x="891" y="631" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="648" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="648" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> System Settings </text>
<text x="891" y="648" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="665" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="665" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="682" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="682" fill="#6c7086" textLength="657" lengthAdjust="spacingAndGlyphs">(Use Enter to select, Ctrl+L to reset, Tab to change focus, Esc to close)</text>
<text x="891" y="682" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="699" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="699" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="716" fill="#3d3f51" textLength="900" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────────╯</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

@@ -43,8 +43,7 @@ exports[`SettingsDialog > Initial Rendering > should render settings list with v
│ │
│ (Use Enter to select, Ctrl+L to reset, Tab to change focus, Esc to close) │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
"
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`SettingsDialog > Snapshot Tests > should render 'accessibility settings enabled' correctly 1`] = `
@@ -90,8 +89,7 @@ exports[`SettingsDialog > Snapshot Tests > should render 'accessibility settings
│ │
│ (Use Enter to select, Ctrl+L to reset, Tab to change focus, Esc to close) │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
"
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`SettingsDialog > Snapshot Tests > should render 'all boolean settings disabled' correctly 1`] = `
@@ -137,8 +135,7 @@ exports[`SettingsDialog > Snapshot Tests > should render 'all boolean settings d
│ │
│ (Use Enter to select, Ctrl+L to reset, Tab to change focus, Esc to close) │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
"
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`SettingsDialog > Snapshot Tests > should render 'default state' correctly 1`] = `
@@ -184,8 +181,7 @@ exports[`SettingsDialog > Snapshot Tests > should render 'default state' correct
│ │
│ (Use Enter to select, Ctrl+L to reset, Tab to change focus, Esc to close) │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
"
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`SettingsDialog > Snapshot Tests > should render 'file filtering settings configured' correctly 1`] = `
@@ -231,8 +227,7 @@ exports[`SettingsDialog > Snapshot Tests > should render 'file filtering setting
│ │
│ (Use Enter to select, Ctrl+L to reset, Tab to change focus, Esc to close) │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
"
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`SettingsDialog > Snapshot Tests > should render 'focused on scope selector' correctly 1`] = `
@@ -278,8 +273,7 @@ exports[`SettingsDialog > Snapshot Tests > should render 'focused on scope selec
│ │
│ (Use Enter to select, Ctrl+L to reset, Tab to change focus, Esc to close) │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
"
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`SettingsDialog > Snapshot Tests > should render 'mixed boolean and number settings' correctly 1`] = `
@@ -325,8 +319,7 @@ exports[`SettingsDialog > Snapshot Tests > should render 'mixed boolean and numb
│ │
│ (Use Enter to select, Ctrl+L to reset, Tab to change focus, Esc to close) │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
"
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`SettingsDialog > Snapshot Tests > should render 'tools and security settings' correctly 1`] = `
@@ -372,8 +365,7 @@ exports[`SettingsDialog > Snapshot Tests > should render 'tools and security set
│ │
│ (Use Enter to select, Ctrl+L to reset, Tab to change focus, Esc to close) │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
"
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`SettingsDialog > Snapshot Tests > should render 'various boolean settings enabled' correctly 1`] = `
@@ -419,6 +411,5 @@ exports[`SettingsDialog > Snapshot Tests > should render 'various boolean settin
│ │
│ (Use Enter to select, Ctrl+L to reset, Tab to change focus, Esc to close) │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
"
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
`;
@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="88" viewBox="0 0 920 88">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="88" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs">ID Name </text>
<text x="0" y="19" fill="#3d3f51" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
<text x="0" y="36" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs">1 Alice </text>
<text x="0" y="53" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs">2 Bob </text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="71" viewBox="0 0 920 71">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="71" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs">Value </text>
<text x="0" y="19" fill="#3d3f51" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
<text x="0" y="36" fill="#00cd00" textLength="18" lengthAdjust="spacingAndGlyphs">20</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="71" viewBox="0 0 920 71">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="71" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs">Status </text>
<text x="0" y="19" fill="#3d3f51" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
<rect x="0" y="34" width="54" height="17" fill="#ffffff" />
<text x="0" y="36" fill="#000000" textLength="54" lengthAdjust="spacingAndGlyphs">Active</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -4,13 +4,17 @@ exports[`Table > should render headers and data correctly 1`] = `
"ID Name
────────────────────────────────────────────────────────────────────────────────────────────────────
1 Alice
2 Bob
"
2 Bob"
`;
exports[`Table > should support custom cell rendering 1`] = `
"Value
────────────────────────────────────────────────────────────────────────────────────────────────────
20
"
20"
`;
exports[`Table > should support inverse text rendering 1`] = `
"Status
────────────────────────────────────────────────────────────────────────────────────────────────────
Active"
`;
@@ -85,7 +85,7 @@ exports[`ToolConfirmationQueue > renders ExitPlanMode tool confirmation with Suc
│ Approves plan but requires confirmation for each tool │
│ 3. Type your feedback... │
│ │
│ Enter to select · ↑/↓ to navigate · Esc to cancel
│ Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
╰──────────────────────────────────────────────────────────────────────────────╯
"
`;
@@ -37,6 +37,7 @@ describe('ToolConfirmationMessage Redirection', () => {
callId="test-call-id"
confirmationDetails={confirmationDetails}
config={mockConfig}
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={100}
/>,
@@ -52,6 +52,7 @@ describe('ToolConfirmationMessage', () => {
callId="test-call-id"
confirmationDetails={confirmationDetails}
config={mockConfig}
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
/>,
@@ -78,6 +79,7 @@ describe('ToolConfirmationMessage', () => {
callId="test-call-id"
confirmationDetails={confirmationDetails}
config={mockConfig}
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
/>,
@@ -101,6 +103,7 @@ describe('ToolConfirmationMessage', () => {
callId="test-call-id"
confirmationDetails={confirmationDetails}
config={mockConfig}
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
/>,
@@ -131,6 +134,7 @@ describe('ToolConfirmationMessage', () => {
callId="test-call-id"
confirmationDetails={confirmationDetails}
config={mockConfig}
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
/>,
@@ -161,6 +165,7 @@ describe('ToolConfirmationMessage', () => {
callId="test-call-id"
confirmationDetails={confirmationDetails}
config={mockConfig}
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
/>,
@@ -190,6 +195,7 @@ describe('ToolConfirmationMessage', () => {
callId="test-call-id"
confirmationDetails={confirmationDetails}
config={mockConfig}
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
/>,
@@ -219,6 +225,7 @@ describe('ToolConfirmationMessage', () => {
callId="test-call-id"
confirmationDetails={confirmationDetails}
config={mockConfig}
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
/>,
@@ -300,6 +307,7 @@ describe('ToolConfirmationMessage', () => {
callId="test-call-id"
confirmationDetails={details}
config={mockConfig}
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
/>,
@@ -321,6 +329,7 @@ describe('ToolConfirmationMessage', () => {
callId="test-call-id"
confirmationDetails={details}
config={mockConfig}
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
/>,
@@ -355,6 +364,7 @@ describe('ToolConfirmationMessage', () => {
callId="test-call-id"
confirmationDetails={editConfirmationDetails}
config={mockConfig}
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
/>,
@@ -381,6 +391,7 @@ describe('ToolConfirmationMessage', () => {
callId="test-call-id"
confirmationDetails={editConfirmationDetails}
config={mockConfig}
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
/>,
@@ -425,6 +436,7 @@ describe('ToolConfirmationMessage', () => {
callId="test-call-id"
confirmationDetails={editConfirmationDetails}
config={mockConfig}
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
/>,
@@ -452,6 +464,7 @@ describe('ToolConfirmationMessage', () => {
callId="test-call-id"
confirmationDetails={editConfirmationDetails}
config={mockConfig}
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
/>,
@@ -479,6 +492,7 @@ describe('ToolConfirmationMessage', () => {
callId="test-call-id"
confirmationDetails={editConfirmationDetails}
config={mockConfig}
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
/>,
@@ -505,6 +519,7 @@ describe('ToolConfirmationMessage', () => {
callId="test-call-id"
confirmationDetails={confirmationDetails}
config={mockConfig}
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
/>,
@@ -550,6 +565,7 @@ describe('ToolConfirmationMessage', () => {
callId="test-call-id"
confirmationDetails={confirmationDetails}
config={mockConfig}
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
/>,
@@ -581,6 +597,7 @@ describe('ToolConfirmationMessage', () => {
callId="test-call-id"
confirmationDetails={confirmationDetails}
config={mockConfig}
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
/>,
@@ -14,6 +14,7 @@ import {
type Config,
type ToolConfirmationPayload,
ToolConfirmationOutcome,
type EditorType,
hasRedirection,
debugLogger,
} from '@google/gemini-cli-core';
@@ -49,6 +50,7 @@ export interface ToolConfirmationMessageProps {
callId: string;
confirmationDetails: SerializableConfirmationDetails;
config: Config;
getPreferredEditor: () => EditorType | undefined;
isFocused?: boolean;
availableTerminalHeight?: number;
terminalWidth: number;
@@ -60,6 +62,7 @@ export const ToolConfirmationMessage: React.FC<
callId,
confirmationDetails,
config,
getPreferredEditor,
isFocused = true,
availableTerminalHeight,
terminalWidth,
@@ -424,6 +427,7 @@ export const ToolConfirmationMessage: React.FC<
bodyContent = (
<ExitPlanModeDialog
planPath={confirmationDetails.planPath}
getPreferredEditor={getPreferredEditor}
onApprove={(approvalMode) => {
handleConfirm(ToolConfirmationOutcome.ProceedOnce, {
approved: true,
@@ -629,6 +633,7 @@ export const ToolConfirmationMessage: React.FC<
hasMcpToolDetails,
mcpToolDetailsText,
expandDetailsHintKey,
getPreferredEditor,
]);
const bodyOverflowDirection: 'top' | 'bottom' =
@@ -2,29 +2,29 @@
exports[`UserMessage > renders multiline user message 1`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> Line 1
Line 2
> Line 1
Line 2
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
"
`;
exports[`UserMessage > renders normal user message with correct prefix 1`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> Hello Gemini
> Hello Gemini
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
"
`;
exports[`UserMessage > renders slash command message 1`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> /help
> /help
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
"
`;
exports[`UserMessage > transforms image paths in user message 1`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> Check out this image: [Image my-image.png]
> Check out this image: [Image my-image.png]
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
"
`;
@@ -15,6 +15,8 @@ export interface DialogFooterProps {
navigationActions?: string;
/** Exit shortcut (defaults to "Esc to cancel") */
cancelAction?: string;
/** Custom keyboard shortcut hints (e.g., ["Ctrl+P to edit"]) */
extraParts?: string[];
}
/**
@@ -25,11 +27,13 @@ export const DialogFooter: React.FC<DialogFooterProps> = ({
primaryAction,
navigationActions,
cancelAction = 'Esc to cancel',
extraParts = [],
}) => {
const parts = [primaryAction];
if (navigationActions) {
parts.push(navigationActions);
}
parts.push(...extraParts);
parts.push(cancelAction);
return (
@@ -4,7 +4,6 @@
* SPDX-License-Identifier: Apache-2.0
*/
import chalk from 'chalk';
import { describe, it, expect } from 'vitest';
import { render } from '../../../test-utils/render.js';
import { ExpandableText, MAX_WIDTH } from './ExpandableText.js';
@@ -14,7 +13,7 @@ describe('ExpandableText', () => {
const flat = (s: string | undefined) => (s ?? '').replace(/\n/g, '');
it('renders plain label when no match (short label)', async () => {
const { lastFrame, waitUntilReady, unmount } = render(
const renderResult = render(
<ExpandableText
label="simple command"
userInput=""
@@ -23,14 +22,15 @@ describe('ExpandableText', () => {
isExpanded={false}
/>,
);
const { waitUntilReady, unmount } = renderResult;
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
await expect(renderResult).toMatchSvgSnapshot();
unmount();
});
it('truncates long label when collapsed and no match', async () => {
const long = 'x'.repeat(MAX_WIDTH + 25);
const { lastFrame, waitUntilReady, unmount } = render(
const renderResult = render(
<ExpandableText
label={long}
userInput=""
@@ -38,18 +38,19 @@ describe('ExpandableText', () => {
isExpanded={false}
/>,
);
const { lastFrame, waitUntilReady, unmount } = renderResult;
await waitUntilReady();
const out = lastFrame();
const f = flat(out);
expect(f.endsWith('...')).toBe(true);
expect(f.length).toBe(MAX_WIDTH + 3);
expect(out).toMatchSnapshot();
await expect(renderResult).toMatchSvgSnapshot();
unmount();
});
it('shows full long label when expanded and no match', async () => {
const long = 'y'.repeat(MAX_WIDTH + 25);
const { lastFrame, waitUntilReady, unmount } = render(
const renderResult = render(
<ExpandableText
label={long}
userInput=""
@@ -57,11 +58,12 @@ describe('ExpandableText', () => {
isExpanded={true}
/>,
);
const { lastFrame, waitUntilReady, unmount } = renderResult;
await waitUntilReady();
const out = lastFrame();
const f = flat(out);
expect(f.length).toBe(long.length);
expect(out).toMatchSnapshot();
await expect(renderResult).toMatchSvgSnapshot();
unmount();
});
@@ -69,7 +71,7 @@ describe('ExpandableText', () => {
const label = 'run: git commit -m "feat: add search"';
const userInput = 'commit';
const matchedIndex = label.indexOf(userInput);
const { lastFrame, waitUntilReady, unmount } = render(
const renderResult = render(
<ExpandableText
label={label}
userInput={userInput}
@@ -79,9 +81,9 @@ describe('ExpandableText', () => {
/>,
100,
);
const { waitUntilReady, unmount } = renderResult;
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
expect(lastFrame()).toContain(chalk.inverse(userInput));
await expect(renderResult).toMatchSvgSnapshot();
unmount();
});
@@ -91,7 +93,7 @@ describe('ExpandableText', () => {
const suffix = '/and/then/some/more/components/'.repeat(3);
const label = prefix + core + suffix;
const matchedIndex = prefix.length;
const { lastFrame, waitUntilReady, unmount } = render(
const renderResult = render(
<ExpandableText
label={label}
userInput={core}
@@ -101,13 +103,14 @@ describe('ExpandableText', () => {
/>,
100,
);
const { lastFrame, waitUntilReady, unmount } = renderResult;
await waitUntilReady();
const out = lastFrame();
const f = flat(out);
expect(f.includes(core)).toBe(true);
expect(f.startsWith('...')).toBe(true);
expect(f.endsWith('...')).toBe(true);
expect(out).toMatchSnapshot();
await expect(renderResult).toMatchSvgSnapshot();
unmount();
});
@@ -117,7 +120,7 @@ describe('ExpandableText', () => {
const suffix = ' in this text';
const label = prefix + core + suffix;
const matchedIndex = prefix.length;
const { lastFrame, waitUntilReady, unmount } = render(
const renderResult = render(
<ExpandableText
label={label}
userInput={core}
@@ -126,6 +129,7 @@ describe('ExpandableText', () => {
isExpanded={false}
/>,
);
const { lastFrame, waitUntilReady, unmount } = renderResult;
await waitUntilReady();
const out = lastFrame();
const f = flat(out);
@@ -133,14 +137,14 @@ describe('ExpandableText', () => {
expect(f.startsWith('...')).toBe(false);
expect(f.endsWith('...')).toBe(true);
expect(f.length).toBe(MAX_WIDTH + 2);
expect(out).toMatchSnapshot();
await expect(renderResult).toMatchSvgSnapshot();
unmount();
});
it('respects custom maxWidth', async () => {
const customWidth = 50;
const long = 'z'.repeat(100);
const { lastFrame, waitUntilReady, unmount } = render(
const renderResult = render(
<ExpandableText
label={long}
userInput=""
@@ -149,12 +153,13 @@ describe('ExpandableText', () => {
maxWidth={customWidth}
/>,
);
const { lastFrame, waitUntilReady, unmount } = renderResult;
await waitUntilReady();
const out = lastFrame();
const f = flat(out);
expect(f.endsWith('...')).toBe(true);
expect(f.length).toBe(customWidth + 3);
expect(out).toMatchSnapshot();
await expect(renderResult).toMatchSvgSnapshot();
unmount();
});
});
@@ -11,7 +11,7 @@ exports[`<EnumSelector /> > renders with numeric options and matches snapshot 1`
`;
exports[`<EnumSelector /> > renders with single option and matches snapshot 1`] = `
" Only Option
" Only Option
"
`;
@@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="54" viewBox="0 0 920 54">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="54" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#e5e5e5" textLength="621" lengthAdjust="spacingAndGlyphs">...ry/long/path/that/keeps/going/cd_/very/long/path/that/keeps/going/</text>
<rect x="621" y="0" width="99" height="17" fill="#e5e5e5" />
<text x="621" y="2" fill="#000000" textLength="99" lengthAdjust="spacingAndGlyphs">search-here</text>
<text x="720" y="2" fill="#e5e5e5" textLength="180" lengthAdjust="spacingAndGlyphs">/and/then/some/more/</text>
<text x="0" y="19" fill="#e5e5e5" textLength="450" lengthAdjust="spacingAndGlyphs">components//and/then/some/more/components//and/...</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 935 B

@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="37" viewBox="0 0 920 37">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="37" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#e5e5e5" textLength="81" lengthAdjust="spacingAndGlyphs">run: git </text>
<rect x="81" y="0" width="54" height="17" fill="#e5e5e5" />
<text x="81" y="2" fill="#000000" textLength="54" lengthAdjust="spacingAndGlyphs">commit</text>
<text x="135" y="2" fill="#e5e5e5" textLength="198" lengthAdjust="spacingAndGlyphs"> -m &quot;feat: add search&quot;</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 734 B

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="37" viewBox="0 0 920 37">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="37" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#e5e5e5" textLength="126" lengthAdjust="spacingAndGlyphs">simple command</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 448 B

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="37" viewBox="0 0 920 37">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="37" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#e5e5e5" textLength="477" lengthAdjust="spacingAndGlyphs">zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz...</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 487 B

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="54" viewBox="0 0 920 54">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="54" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#e5e5e5" textLength="900" lengthAdjust="spacingAndGlyphs">yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy</text>
<text x="0" y="19" fill="#e5e5e5" textLength="675" lengthAdjust="spacingAndGlyphs">yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 704 B

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="54" viewBox="0 0 920 54">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="54" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#e5e5e5" textLength="900" lengthAdjust="spacingAndGlyphs">xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</text>
<text x="0" y="19" fill="#e5e5e5" textLength="477" lengthAdjust="spacingAndGlyphs">xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 682 B

@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="54" viewBox="0 0 920 54">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="54" fill="#000000" />
<g transform="translate(10, 10)">
<rect x="0" y="0" width="900" height="17" fill="#e5e5e5" />
<text x="0" y="2" fill="#000000" textLength="900" lengthAdjust="spacingAndGlyphs">xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</text>
<rect x="0" y="17" width="468" height="17" fill="#e5e5e5" />
<text x="0" y="19" fill="#000000" textLength="468" lengthAdjust="spacingAndGlyphs">xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 810 B

@@ -2,39 +2,26 @@
exports[`ExpandableText > creates centered window around match when collapsed 1`] = `
"...ry/long/path/that/keeps/going/cd_/very/long/path/that/keeps/going/search-here/and/then/some/more/
components//and/then/some/more/components//and/...
"
components//and/then/some/more/components//and/..."
`;
exports[`ExpandableText > highlights matched substring when expanded (text only visible) 1`] = `
"run: git commit -m "feat: add search"
"
`;
exports[`ExpandableText > highlights matched substring when expanded (text only visible) 1`] = `"run: git commit -m "feat: add search""`;
exports[`ExpandableText > renders plain label when no match (short label) 1`] = `
"simple command
"
`;
exports[`ExpandableText > renders plain label when no match (short label) 1`] = `"simple command"`;
exports[`ExpandableText > respects custom maxWidth 1`] = `
"zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz...
"
`;
exports[`ExpandableText > respects custom maxWidth 1`] = `"zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz..."`;
exports[`ExpandableText > shows full long label when expanded and no match 1`] = `
"yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
"
yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
`;
exports[`ExpandableText > truncates long label when collapsed and no match 1`] = `
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...
"
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx..."
`;
exports[`ExpandableText > truncates match itself when match is very long 1`] = `
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...
"
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx..."
`;

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