mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-08-02 21:21:09 -07:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 94ccdc75c2 | |||
| 5e80faf389 | |||
| c2e92125c3 | |||
| 302b48a0b3 | |||
| 8fa4585ebf | |||
| 5d2983f12a | |||
| 9912ea8686 | |||
| 508aaeda89 | |||
| a6256cdabf | |||
| c316fc6c51 | |||
| 7e6e40c814 | |||
| b25c8137f6 | |||
| 03a8fc3113 | |||
| 393e9a171f | |||
| 59702f913d | |||
| b3439e1458 | |||
| b21b289e26 | |||
| 56a63a35e2 |
@@ -124,7 +124,9 @@ jobs:
|
||||
- name: 'Link Checker'
|
||||
uses: 'lycheeverse/lychee-action@885c65f3dc543b57c898c8099f4e08c8afd178a2' # ratchet: lycheeverse/lychee-action@v2.6.1
|
||||
with:
|
||||
args: '--verbose --accept 200,503 ./**/*.md'
|
||||
# Exclude GEMINI.md because the absolute GitHub URL in CONTRIBUTING.md (which is symlinked)
|
||||
# causes intermittent 429 Too Many Requests errors from GitHub API rate limits.
|
||||
args: '--verbose --accept 200,503 --exclude "GEMINI\\.md" ./**/*.md'
|
||||
fail: true
|
||||
test_linux:
|
||||
name: 'Test (Linux) - ${{ matrix.node-version }}, ${{ matrix.shard }}'
|
||||
|
||||
@@ -22,4 +22,6 @@ jobs:
|
||||
id: 'lychee'
|
||||
uses: 'lycheeverse/lychee-action@885c65f3dc543b57c898c8099f4e08c8afd178a2' # ratchet: lycheeverse/lychee-action@v2.6.1
|
||||
with:
|
||||
args: '--verbose --no-progress --accept 200,503 ./**/*.md'
|
||||
# Exclude GEMINI.md because the absolute GitHub URL in CONTRIBUTING.md (which is symlinked)
|
||||
# causes intermittent 429 Too Many Requests errors from GitHub API rate limits.
|
||||
args: '--verbose --no-progress --accept 200,503 --exclude "GEMINI\\.md" ./**/*.md'
|
||||
|
||||
@@ -73,10 +73,6 @@ powerful tool for developers.
|
||||
and `packages/core` (Backend logic).
|
||||
- **Imports:** Use specific imports and avoid restricted relative imports
|
||||
between packages (enforced by ESLint).
|
||||
- **Linting:** Never suppress the `@typescript-eslint/no-unnecessary-condition`
|
||||
warning (e.g., via `eslint-disable`). This rule ensures that optional chaining
|
||||
and truthiness checks are only used when truly necessary according to the type
|
||||
system, keeping the code clean and predictable.
|
||||
- **License Headers:** For all new source code files (`.ts`, `.tsx`, `.js`),
|
||||
include the Apache-2.0 license header with the current year. (e.g.,
|
||||
`Copyright 2026 Google LLC`). This is enforced by ESLint.
|
||||
|
||||
+189
-163
@@ -1,6 +1,6 @@
|
||||
# Preview release: v0.33.0-preview.0
|
||||
# Preview release: v0.32.0-preview.0
|
||||
|
||||
Released: March 03, 2026
|
||||
Released: February 27, 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,170 +13,196 @@ npm install -g @google/gemini-cli@preview
|
||||
|
||||
## Highlights
|
||||
|
||||
- **Plan Mode Enhancements**: Added support for annotating plans with feedback
|
||||
for iteration, enabling built-in research subagents in plan mode, and a new
|
||||
`copy` subcommand.
|
||||
- **Agent and Skill Improvements**: Introduced the new `github-issue-creator`
|
||||
skill, implemented HTTP authentication support for A2A remote agents, and
|
||||
added support for authenticated A2A agent card discovery.
|
||||
- **CLI UX/UI Updates**: Redesigned the header to be compact with an ASCII icon,
|
||||
inverted the context window display to show usage, and directly indicate auth
|
||||
required state for agents.
|
||||
- **Core and ACP Enhancements**: Implemented slash command handling in ACP (for
|
||||
`/memory`, `/init`, `/extensions`, and `/restore`), added a set models
|
||||
interface to ACP, and centralized `read_file` limits while truncating large
|
||||
MCP tool output.
|
||||
- **Plan Mode Enhancements**: Significant updates to Plan Mode, including
|
||||
support for modifying plans in external editors, adaptive workflows based on
|
||||
task complexity, and new integration tests.
|
||||
- **Agent and Core Engine Updates**: Enabled the generalist agent, introduced
|
||||
`Kind.Agent` for sub-agent classification, implemented task tracking
|
||||
foundation, and improved Agent-to-Agent (A2A) streaming and content
|
||||
extraction.
|
||||
- **CLI & User Experience**: Introduced interactive shell autocompletion, added
|
||||
a new verbosity mode for cleaner error reporting, enabled parallel loading of
|
||||
extensions, and improved UI hints and shortcut handling.
|
||||
- **Billing and Security**: Implemented G1 AI credits overage flow with enhanced
|
||||
billing telemetry, updated the authentication handshake to specification, and
|
||||
added support for a policy engine in extensions.
|
||||
- **Stability and Bug Fixes**: Addressed numerous issues including 100% CPU
|
||||
consumption by orphaned processes, enhanced retry logic for Code Assist,
|
||||
reduced intrusive MCP errors, and merged duplicate imports across packages.
|
||||
|
||||
## What's Changed
|
||||
|
||||
- Docs: Update model docs to remove Preview Features. by @jkcinouye in
|
||||
[#20084](https://github.com/google-gemini/gemini-cli/pull/20084)
|
||||
- docs: fix typo in installation documentation by @AdityaSharma-Git3207 in
|
||||
[#20153](https://github.com/google-gemini/gemini-cli/pull/20153)
|
||||
- docs: add Windows PowerShell equivalents for environments and scripting by
|
||||
@scidomino in [#20333](https://github.com/google-gemini/gemini-cli/pull/20333)
|
||||
- fix(core): parse raw ASCII buffer strings in Gaxios errors by @sehoon38 in
|
||||
[#20626](https://github.com/google-gemini/gemini-cli/pull/20626)
|
||||
- chore(release): bump version to 0.33.0-nightly.20260227.ba149afa0 by @galz10
|
||||
in [#20637](https://github.com/google-gemini/gemini-cli/pull/20637)
|
||||
- fix(github): use robot PAT for automated PRs to pass CLA check by @galz10 in
|
||||
[#20641](https://github.com/google-gemini/gemini-cli/pull/20641)
|
||||
- chore/release: bump version to 0.33.0-nightly.20260228.1ca5c05d0 by
|
||||
@gemini-cli-robot in
|
||||
[#20644](https://github.com/google-gemini/gemini-cli/pull/20644)
|
||||
- Changelog for v0.31.0 by @gemini-cli-robot in
|
||||
[#20634](https://github.com/google-gemini/gemini-cli/pull/20634)
|
||||
- fix: use full paths for ACP diff payloads by @JagjeevanAK in
|
||||
[#19539](https://github.com/google-gemini/gemini-cli/pull/19539)
|
||||
- Changelog for v0.32.0-preview.0 by @gemini-cli-robot in
|
||||
[#20627](https://github.com/google-gemini/gemini-cli/pull/20627)
|
||||
- fix: acp/zed race condition between MCP initialisation and prompt by
|
||||
@kartikangiras in
|
||||
[#20205](https://github.com/google-gemini/gemini-cli/pull/20205)
|
||||
- fix(cli): reset themeManager between tests to ensure isolation by
|
||||
@NTaylorMullen in
|
||||
[#20598](https://github.com/google-gemini/gemini-cli/pull/20598)
|
||||
- refactor(core): Extract tool parameter names as constants by @SandyTao520 in
|
||||
[#20460](https://github.com/google-gemini/gemini-cli/pull/20460)
|
||||
- fix(cli): resolve autoThemeSwitching when background hasn't changed but theme
|
||||
mismatches by @sehoon38 in
|
||||
[#20706](https://github.com/google-gemini/gemini-cli/pull/20706)
|
||||
- feat(skills): add github-issue-creator skill by @sehoon38 in
|
||||
[#20709](https://github.com/google-gemini/gemini-cli/pull/20709)
|
||||
- fix(cli): allow sub-agent confirmation requests in UI while preventing
|
||||
background flicker by @abhipatel12 in
|
||||
[#20722](https://github.com/google-gemini/gemini-cli/pull/20722)
|
||||
- Merge User and Agent Card Descriptions #20849 by @adamfweidman in
|
||||
[#20850](https://github.com/google-gemini/gemini-cli/pull/20850)
|
||||
- fix(core): reduce LLM-based loop detection false positives by @SandyTao520 in
|
||||
[#20701](https://github.com/google-gemini/gemini-cli/pull/20701)
|
||||
- fix(plan): deflake plan mode integration tests by @Adib234 in
|
||||
[#20477](https://github.com/google-gemini/gemini-cli/pull/20477)
|
||||
- Add /unassign support by @scidomino in
|
||||
[#20864](https://github.com/google-gemini/gemini-cli/pull/20864)
|
||||
- feat(core): implement HTTP authentication support for A2A remote agents by
|
||||
- feat(plan): add integration tests for plan mode by @Adib234 in
|
||||
[#20214](https://github.com/google-gemini/gemini-cli/pull/20214)
|
||||
- fix(acp): update auth handshake to spec by @skeshive in
|
||||
[#19725](https://github.com/google-gemini/gemini-cli/pull/19725)
|
||||
- feat(core): implement robust A2A streaming reassembly and fix task continuity
|
||||
by @adamfweidman in
|
||||
[#20091](https://github.com/google-gemini/gemini-cli/pull/20091)
|
||||
- feat(cli): load extensions in parallel by @scidomino in
|
||||
[#20229](https://github.com/google-gemini/gemini-cli/pull/20229)
|
||||
- Plumb the maxAttempts setting through Config args by @kevinjwang1 in
|
||||
[#20239](https://github.com/google-gemini/gemini-cli/pull/20239)
|
||||
- fix(cli): skip 404 errors in setup-github file downloads by @h30s in
|
||||
[#20287](https://github.com/google-gemini/gemini-cli/pull/20287)
|
||||
- fix(cli): expose model.name setting in settings dialog for persistence by
|
||||
@achaljhawar in
|
||||
[#19605](https://github.com/google-gemini/gemini-cli/pull/19605)
|
||||
- docs: remove legacy cmd examples in favor of powershell by @scidomino in
|
||||
[#20323](https://github.com/google-gemini/gemini-cli/pull/20323)
|
||||
- feat(core): Enable model steering in workspace. by @joshualitt in
|
||||
[#20343](https://github.com/google-gemini/gemini-cli/pull/20343)
|
||||
- fix: remove trailing comma in issue triage workflow settings json by @Nixxx19
|
||||
in [#20265](https://github.com/google-gemini/gemini-cli/pull/20265)
|
||||
- feat(core): implement task tracker foundation and service by @anj-s in
|
||||
[#19464](https://github.com/google-gemini/gemini-cli/pull/19464)
|
||||
- test: support tests that include color information by @jacob314 in
|
||||
[#20220](https://github.com/google-gemini/gemini-cli/pull/20220)
|
||||
- feat(core): introduce Kind.Agent for sub-agent classification by @abhipatel12
|
||||
in [#20369](https://github.com/google-gemini/gemini-cli/pull/20369)
|
||||
- Changelog for v0.30.0 by @gemini-cli-robot in
|
||||
[#20252](https://github.com/google-gemini/gemini-cli/pull/20252)
|
||||
- Update changelog workflow to reject nightly builds by @g-samroberts in
|
||||
[#20248](https://github.com/google-gemini/gemini-cli/pull/20248)
|
||||
- Changelog for v0.31.0-preview.0 by @gemini-cli-robot in
|
||||
[#20249](https://github.com/google-gemini/gemini-cli/pull/20249)
|
||||
- feat(cli): hide workspace policy update dialog and auto-accept by default by
|
||||
@Abhijit-2592 in
|
||||
[#20351](https://github.com/google-gemini/gemini-cli/pull/20351)
|
||||
- feat(core): rename grep_search include parameter to include_pattern by
|
||||
@SandyTao520 in
|
||||
[#20510](https://github.com/google-gemini/gemini-cli/pull/20510)
|
||||
- feat(core): centralize read_file limits and update gemini-3 description by
|
||||
@aishaneeshah in
|
||||
[#20619](https://github.com/google-gemini/gemini-cli/pull/20619)
|
||||
- Do not block CI on evals by @gundermanc in
|
||||
[#20870](https://github.com/google-gemini/gemini-cli/pull/20870)
|
||||
- document node limitation for shift+tab by @scidomino in
|
||||
[#20877](https://github.com/google-gemini/gemini-cli/pull/20877)
|
||||
- Add install as an option when extension is selected. by @DavidAPierce in
|
||||
[#20358](https://github.com/google-gemini/gemini-cli/pull/20358)
|
||||
- Update CODEOWNERS for README.md reviewers by @g-samroberts in
|
||||
[#20860](https://github.com/google-gemini/gemini-cli/pull/20860)
|
||||
- feat(core): truncate large MCP tool output by @SandyTao520 in
|
||||
[#19365](https://github.com/google-gemini/gemini-cli/pull/19365)
|
||||
- Subagent activity UX. by @gundermanc in
|
||||
[#17570](https://github.com/google-gemini/gemini-cli/pull/17570)
|
||||
- style(cli) : Dialog pattern for /hooks Command by @AbdulTawabJuly in
|
||||
[#17930](https://github.com/google-gemini/gemini-cli/pull/17930)
|
||||
- feat: redesign header to be compact with ASCII icon by @keithguerin in
|
||||
[#18713](https://github.com/google-gemini/gemini-cli/pull/18713)
|
||||
- fix(core): ensure subagents use qualified MCP tool names by @abhipatel12 in
|
||||
[#20801](https://github.com/google-gemini/gemini-cli/pull/20801)
|
||||
- feat(core): support authenticated A2A agent card discovery by @SandyTao520 in
|
||||
[#20622](https://github.com/google-gemini/gemini-cli/pull/20622)
|
||||
- refactor(cli): fully remove React anti patterns, improve type safety and fix
|
||||
UX oversights in SettingsDialog.tsx by @psinha40898 in
|
||||
[#18963](https://github.com/google-gemini/gemini-cli/pull/18963)
|
||||
- Adding MCPOAuthProvider implementing the MCPSDK OAuthClientProvider by
|
||||
@Nayana-Parameswarappa in
|
||||
[#20121](https://github.com/google-gemini/gemini-cli/pull/20121)
|
||||
- feat(core): add tool name validation in TOML policy files by @allenhutchison
|
||||
in [#19281](https://github.com/google-gemini/gemini-cli/pull/19281)
|
||||
- docs: fix broken markdown links in main README.md by @Hamdanbinhashim in
|
||||
[#20300](https://github.com/google-gemini/gemini-cli/pull/20300)
|
||||
- refactor(core): replace manual syncPlanModeTools with declarative policy rules
|
||||
by @jerop in [#20596](https://github.com/google-gemini/gemini-cli/pull/20596)
|
||||
- fix(core): increase default headers timeout to 5 minutes by @gundermanc in
|
||||
[#20890](https://github.com/google-gemini/gemini-cli/pull/20890)
|
||||
- feat(admin): enable 30 day default retention for chat history & remove warning
|
||||
by @skeshive in
|
||||
[#20853](https://github.com/google-gemini/gemini-cli/pull/20853)
|
||||
- feat(plan): support annotating plans with feedback for iteration by @Adib234
|
||||
in [#20876](https://github.com/google-gemini/gemini-cli/pull/20876)
|
||||
- Add some dos and don'ts to behavioral evals README. by @gundermanc in
|
||||
[#20629](https://github.com/google-gemini/gemini-cli/pull/20629)
|
||||
- fix(core): skip telemetry logging for AbortError exceptions by @yunaseoul in
|
||||
[#19477](https://github.com/google-gemini/gemini-cli/pull/19477)
|
||||
- fix(core): restrict "System: Please continue" invalid stream retry to Gemini 2
|
||||
models by @SandyTao520 in
|
||||
[#20897](https://github.com/google-gemini/gemini-cli/pull/20897)
|
||||
- ci(evals): only run evals in CI if prompts or tools changed by @gundermanc in
|
||||
[#20898](https://github.com/google-gemini/gemini-cli/pull/20898)
|
||||
- Build binary by @aswinashok44 in
|
||||
[#18933](https://github.com/google-gemini/gemini-cli/pull/18933)
|
||||
- Code review fixes as a pr by @jacob314 in
|
||||
[#20612](https://github.com/google-gemini/gemini-cli/pull/20612)
|
||||
- fix(ci): handle empty APP_ID in stale PR closer by @bdmorgan in
|
||||
[#20919](https://github.com/google-gemini/gemini-cli/pull/20919)
|
||||
- feat(cli): invert context window display to show usage by @keithguerin in
|
||||
[#20071](https://github.com/google-gemini/gemini-cli/pull/20071)
|
||||
- fix(plan): clean up session directories and plans on deletion by @jerop in
|
||||
[#20914](https://github.com/google-gemini/gemini-cli/pull/20914)
|
||||
- fix(core): enforce optionality for API response fields in code_assist by
|
||||
@sehoon38 in [#20714](https://github.com/google-gemini/gemini-cli/pull/20714)
|
||||
- feat(extensions): add support for plan directory in extension manifest by
|
||||
@mahimashanware in
|
||||
[#20354](https://github.com/google-gemini/gemini-cli/pull/20354)
|
||||
- feat(plan): enable built-in research subagents in plan mode by @Adib234 in
|
||||
[#20972](https://github.com/google-gemini/gemini-cli/pull/20972)
|
||||
- feat(agents): directly indicate auth required state by @adamfweidman in
|
||||
[#20986](https://github.com/google-gemini/gemini-cli/pull/20986)
|
||||
- fix(cli): wait for background auto-update before relaunching by @scidomino in
|
||||
[#20904](https://github.com/google-gemini/gemini-cli/pull/20904)
|
||||
- fix: pre-load @scripts/copy_files.js references from external editor prompts
|
||||
by @kartikangiras in
|
||||
[#20963](https://github.com/google-gemini/gemini-cli/pull/20963)
|
||||
- feat(evals): add behavioral evals for ask_user tool by @Adib234 in
|
||||
[#20620](https://github.com/google-gemini/gemini-cli/pull/20620)
|
||||
- refactor common settings logic for skills,agents by @ishaanxgupta in
|
||||
[#17490](https://github.com/google-gemini/gemini-cli/pull/17490)
|
||||
- Update docs-writer skill with new resource by @g-samroberts in
|
||||
[#20917](https://github.com/google-gemini/gemini-cli/pull/20917)
|
||||
- fix(cli): pin clipboardy to ~5.2.x by @scidomino in
|
||||
[#21009](https://github.com/google-gemini/gemini-cli/pull/21009)
|
||||
- feat: Implement slash command handling in ACP for
|
||||
`/memory`,`/init`,`/extensions` and `/restore` by @sripasg in
|
||||
[#20528](https://github.com/google-gemini/gemini-cli/pull/20528)
|
||||
- Docs/add hooks reference by @AadithyaAle in
|
||||
[#20961](https://github.com/google-gemini/gemini-cli/pull/20961)
|
||||
- feat(plan): add copy subcommand to plan (#20491) by @ruomengz in
|
||||
[#20988](https://github.com/google-gemini/gemini-cli/pull/20988)
|
||||
- fix(core): sanitize and length-check MCP tool qualified names by @abhipatel12
|
||||
in [#20987](https://github.com/google-gemini/gemini-cli/pull/20987)
|
||||
- Format the quota/limit style guide. by @g-samroberts in
|
||||
[#21017](https://github.com/google-gemini/gemini-cli/pull/21017)
|
||||
- fix(core): send shell output to model on cancel by @devr0306 in
|
||||
[#20501](https://github.com/google-gemini/gemini-cli/pull/20501)
|
||||
- remove hardcoded tiername when missing tier by @sehoon38 in
|
||||
[#21022](https://github.com/google-gemini/gemini-cli/pull/21022)
|
||||
- feat(acp): add set models interface by @skeshive in
|
||||
[#20991](https://github.com/google-gemini/gemini-cli/pull/20991)
|
||||
[#20328](https://github.com/google-gemini/gemini-cli/pull/20328)
|
||||
- feat(plan): support opening and modifying plan in external editor by @Adib234
|
||||
in [#20348](https://github.com/google-gemini/gemini-cli/pull/20348)
|
||||
- feat(cli): implement interactive shell autocompletion by @mrpmohiburrahman in
|
||||
[#20082](https://github.com/google-gemini/gemini-cli/pull/20082)
|
||||
- fix(core): allow /memory add to work in plan mode by @Jefftree in
|
||||
[#20353](https://github.com/google-gemini/gemini-cli/pull/20353)
|
||||
- feat(core): add HTTP 499 to retryable errors and map to RetryableQuotaError by
|
||||
@bdmorgan in [#20432](https://github.com/google-gemini/gemini-cli/pull/20432)
|
||||
- feat(core): Enable generalist agent by @joshualitt in
|
||||
[#19665](https://github.com/google-gemini/gemini-cli/pull/19665)
|
||||
- Updated tests in TableRenderer.test.tsx to use SVG snapshots by @devr0306 in
|
||||
[#20450](https://github.com/google-gemini/gemini-cli/pull/20450)
|
||||
- Refactor Github Action per b/485167538 by @google-admin in
|
||||
[#19443](https://github.com/google-gemini/gemini-cli/pull/19443)
|
||||
- fix(github): resolve actionlint and yamllint regressions from #19443 by @jerop
|
||||
in [#20467](https://github.com/google-gemini/gemini-cli/pull/20467)
|
||||
- fix: action var usage by @galz10 in
|
||||
[#20492](https://github.com/google-gemini/gemini-cli/pull/20492)
|
||||
- feat(core): improve A2A content extraction by @adamfweidman in
|
||||
[#20487](https://github.com/google-gemini/gemini-cli/pull/20487)
|
||||
- fix(cli): support quota error fallbacks for all authentication types by
|
||||
@sehoon38 in [#20475](https://github.com/google-gemini/gemini-cli/pull/20475)
|
||||
- fix(core): flush transcript for pure tool-call responses to ensure BeforeTool
|
||||
hooks see complete state by @krishdef7 in
|
||||
[#20419](https://github.com/google-gemini/gemini-cli/pull/20419)
|
||||
- feat(plan): adapt planning workflow based on complexity of task by @jerop in
|
||||
[#20465](https://github.com/google-gemini/gemini-cli/pull/20465)
|
||||
- fix: prevent orphaned processes from consuming 100% CPU when terminal closes
|
||||
by @yuvrajangadsingh in
|
||||
[#16965](https://github.com/google-gemini/gemini-cli/pull/16965)
|
||||
- feat(core): increase fetch timeout and fix [object Object] error
|
||||
stringification by @bdmorgan in
|
||||
[#20441](https://github.com/google-gemini/gemini-cli/pull/20441)
|
||||
- [Gemma x Gemini CLI] Add an Experimental Gemma Router that uses a LiteRT-LM
|
||||
shim into the Composite Model Classifier Strategy by @sidwan02 in
|
||||
[#17231](https://github.com/google-gemini/gemini-cli/pull/17231)
|
||||
- docs(plan): update documentation regarding supporting editing of plan files
|
||||
during plan approval by @Adib234 in
|
||||
[#20452](https://github.com/google-gemini/gemini-cli/pull/20452)
|
||||
- test(cli): fix flaky ToolResultDisplay overflow test by @jwhelangoog in
|
||||
[#20518](https://github.com/google-gemini/gemini-cli/pull/20518)
|
||||
- ui(cli): reduce length of Ctrl+O hint by @jwhelangoog in
|
||||
[#20490](https://github.com/google-gemini/gemini-cli/pull/20490)
|
||||
- fix(ui): correct styled table width calculations by @devr0306 in
|
||||
[#20042](https://github.com/google-gemini/gemini-cli/pull/20042)
|
||||
- Avoid overaggressive unescaping by @scidomino in
|
||||
[#20520](https://github.com/google-gemini/gemini-cli/pull/20520)
|
||||
- feat(telemetry) Instrument traces with more attributes and make them available
|
||||
to OTEL users by @heaventourist in
|
||||
[#20237](https://github.com/google-gemini/gemini-cli/pull/20237)
|
||||
- Add support for policy engine in extensions by @chrstnb in
|
||||
[#20049](https://github.com/google-gemini/gemini-cli/pull/20049)
|
||||
- Docs: Update to Terms of Service & FAQ by @jkcinouye in
|
||||
[#20488](https://github.com/google-gemini/gemini-cli/pull/20488)
|
||||
- Fix bottom border rendering for search and add a regression test. by @jacob314
|
||||
in [#20517](https://github.com/google-gemini/gemini-cli/pull/20517)
|
||||
- fix(core): apply retry logic to CodeAssistServer for all users by @bdmorgan in
|
||||
[#20507](https://github.com/google-gemini/gemini-cli/pull/20507)
|
||||
- Fix extension MCP server env var loading by @chrstnb in
|
||||
[#20374](https://github.com/google-gemini/gemini-cli/pull/20374)
|
||||
- feat(ui): add 'ctrl+o' hint to truncated content message by @jerop in
|
||||
[#20529](https://github.com/google-gemini/gemini-cli/pull/20529)
|
||||
- Fix flicker showing message to press ctrl-O again to collapse. by @jacob314 in
|
||||
[#20414](https://github.com/google-gemini/gemini-cli/pull/20414)
|
||||
- fix(cli): hide shortcuts hint while model is thinking or the user has typed a
|
||||
prompt + add debounce to avoid flicker by @jacob314 in
|
||||
[#19389](https://github.com/google-gemini/gemini-cli/pull/19389)
|
||||
- feat(plan): update planning workflow to encourage multi-select with
|
||||
descriptions of options by @Adib234 in
|
||||
[#20491](https://github.com/google-gemini/gemini-cli/pull/20491)
|
||||
- refactor(core,cli): useAlternateBuffer read from config by @psinha40898 in
|
||||
[#20346](https://github.com/google-gemini/gemini-cli/pull/20346)
|
||||
- fix(cli): ensure dialogs stay scrolled to bottom in alternate buffer mode by
|
||||
@jacob314 in [#20527](https://github.com/google-gemini/gemini-cli/pull/20527)
|
||||
- fix(core): revert auto-save of policies to user space by @Abhijit-2592 in
|
||||
[#20531](https://github.com/google-gemini/gemini-cli/pull/20531)
|
||||
- Demote unreliable test. by @gundermanc in
|
||||
[#20571](https://github.com/google-gemini/gemini-cli/pull/20571)
|
||||
- fix(core): handle optional response fields from code assist API by @sehoon38
|
||||
in [#20345](https://github.com/google-gemini/gemini-cli/pull/20345)
|
||||
- fix(cli): keep thought summary when loading phrases are off by @LyalinDotCom
|
||||
in [#20497](https://github.com/google-gemini/gemini-cli/pull/20497)
|
||||
- feat(cli): add temporary flag to disable workspace policies by @Abhijit-2592
|
||||
in [#20523](https://github.com/google-gemini/gemini-cli/pull/20523)
|
||||
- Disable expensive and scheduled workflows on personal forks by @dewitt in
|
||||
[#20449](https://github.com/google-gemini/gemini-cli/pull/20449)
|
||||
- Moved markdown parsing logic to a separate util file by @devr0306 in
|
||||
[#20526](https://github.com/google-gemini/gemini-cli/pull/20526)
|
||||
- fix(plan): prevent agent from using ask_user for shell command confirmation by
|
||||
@Adib234 in [#20504](https://github.com/google-gemini/gemini-cli/pull/20504)
|
||||
- fix(core): disable retries for code assist streaming requests by @sehoon38 in
|
||||
[#20561](https://github.com/google-gemini/gemini-cli/pull/20561)
|
||||
- feat(billing): implement G1 AI credits overage flow with billing telemetry by
|
||||
@gsquared94 in
|
||||
[#18590](https://github.com/google-gemini/gemini-cli/pull/18590)
|
||||
- feat: better error messages by @gsquared94 in
|
||||
[#20577](https://github.com/google-gemini/gemini-cli/pull/20577)
|
||||
- fix(ui): persist expansion in AskUser dialog when navigating options by @jerop
|
||||
in [#20559](https://github.com/google-gemini/gemini-cli/pull/20559)
|
||||
- fix(cli): prevent sub-agent tool calls from leaking into UI by @abhipatel12 in
|
||||
[#20580](https://github.com/google-gemini/gemini-cli/pull/20580)
|
||||
- fix(cli): Shell autocomplete polish by @jacob314 in
|
||||
[#20411](https://github.com/google-gemini/gemini-cli/pull/20411)
|
||||
- Changelog for v0.31.0-preview.1 by @gemini-cli-robot in
|
||||
[#20590](https://github.com/google-gemini/gemini-cli/pull/20590)
|
||||
- Add slash command for promoting behavioral evals to CI blocking by @gundermanc
|
||||
in [#20575](https://github.com/google-gemini/gemini-cli/pull/20575)
|
||||
- Changelog for v0.30.1 by @gemini-cli-robot in
|
||||
[#20589](https://github.com/google-gemini/gemini-cli/pull/20589)
|
||||
- Add low/full CLI error verbosity mode for cleaner UI by @LyalinDotCom in
|
||||
[#20399](https://github.com/google-gemini/gemini-cli/pull/20399)
|
||||
- Disable Gemini PR reviews on draft PRs. by @gundermanc in
|
||||
[#20362](https://github.com/google-gemini/gemini-cli/pull/20362)
|
||||
- Docs: FAQ update by @jkcinouye in
|
||||
[#20585](https://github.com/google-gemini/gemini-cli/pull/20585)
|
||||
- fix(core): reduce intrusive MCP errors and deduplicate diagnostics by
|
||||
@spencer426 in
|
||||
[#20232](https://github.com/google-gemini/gemini-cli/pull/20232)
|
||||
- docs: fix spelling typos in installation guide by @campox747 in
|
||||
[#20579](https://github.com/google-gemini/gemini-cli/pull/20579)
|
||||
- Promote stable tests to CI blocking. by @gundermanc in
|
||||
[#20581](https://github.com/google-gemini/gemini-cli/pull/20581)
|
||||
- feat(core): enable contiguous parallel admission for Kind.Agent tools by
|
||||
@abhipatel12 in
|
||||
[#20583](https://github.com/google-gemini/gemini-cli/pull/20583)
|
||||
- Enforce import/no-duplicates as error by @Nixxx19 in
|
||||
[#19797](https://github.com/google-gemini/gemini-cli/pull/19797)
|
||||
- fix: merge duplicate imports in sdk and test-utils packages (1/4) by @Nixxx19
|
||||
in [#19777](https://github.com/google-gemini/gemini-cli/pull/19777)
|
||||
- fix: merge duplicate imports in a2a-server package (2/4) by @Nixxx19 in
|
||||
[#19781](https://github.com/google-gemini/gemini-cli/pull/19781)
|
||||
|
||||
**Full Changelog**:
|
||||
https://github.com/google-gemini/gemini-cli/compare/v0.32.0-preview.0...v0.33.0-preview.0
|
||||
https://github.com/google-gemini/gemini-cli/compare/v0.31.0-preview.3...v0.32.0-preview.0
|
||||
|
||||
@@ -1014,11 +1014,6 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`experimental.taskTracker`** (boolean):
|
||||
- **Description:** Enable task tracker tools.
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`experimental.modelSteering`** (boolean):
|
||||
- **Description:** Enable model steering (user hints) to guide the model
|
||||
during tool execution.
|
||||
|
||||
@@ -202,7 +202,6 @@ export default tseslint.config(
|
||||
'@typescript-eslint/no-unsafe-type-assertion': 'error',
|
||||
'@typescript-eslint/no-unsafe-assignment': 'error',
|
||||
'@typescript-eslint/no-unsafe-return': 'error',
|
||||
'@typescript-eslint/no-unnecessary-condition': 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -378,213 +377,6 @@ export default tseslint.config(
|
||||
},
|
||||
// Prettier config must be last
|
||||
prettierConfig,
|
||||
{
|
||||
// Legacy files with many @typescript-eslint/no-unnecessary-condition issues
|
||||
files: [
|
||||
'packages/a2a-server/src/agent/executor.ts',
|
||||
'packages/a2a-server/src/agent/task.ts',
|
||||
'packages/a2a-server/src/config/settings.ts',
|
||||
'packages/a2a-server/src/http/app.ts',
|
||||
'packages/cli/src/commands/extensions/configure.ts',
|
||||
'packages/cli/src/config/config.ts',
|
||||
'packages/cli/src/config/extension-manager.ts',
|
||||
'packages/cli/src/config/extensions/extensionEnablement.ts',
|
||||
'packages/cli/src/config/extensions/github.ts',
|
||||
'packages/cli/src/config/mcp/mcpServerEnablement.ts',
|
||||
'packages/cli/src/config/settings-validation.ts',
|
||||
'packages/cli/src/config/settings.ts',
|
||||
'packages/cli/src/config/trustedFolders.ts',
|
||||
'packages/cli/src/nonInteractiveCli.ts',
|
||||
'packages/cli/src/services/McpPromptLoader.ts',
|
||||
'packages/cli/src/test-utils/AppRig.tsx',
|
||||
'packages/cli/src/test-utils/mockConfig.ts',
|
||||
'packages/cli/src/test-utils/render.tsx',
|
||||
'packages/cli/src/ui/AppContainer.tsx',
|
||||
'packages/cli/src/ui/commands/agentsCommand.ts',
|
||||
'packages/cli/src/ui/commands/chatCommand.ts',
|
||||
'packages/cli/src/ui/commands/directoryCommand.tsx',
|
||||
'packages/cli/src/ui/commands/hooksCommand.ts',
|
||||
'packages/cli/src/ui/commands/mcpCommand.ts',
|
||||
'packages/cli/src/ui/commands/restoreCommand.ts',
|
||||
'packages/cli/src/ui/commands/rewindCommand.tsx',
|
||||
'packages/cli/src/ui/commands/setupGithubCommand.ts',
|
||||
'packages/cli/src/ui/commands/skillsCommand.ts',
|
||||
'packages/cli/src/ui/commands/statsCommand.ts',
|
||||
'packages/cli/src/ui/components/AskUserDialog.tsx',
|
||||
'packages/cli/src/ui/components/ColorsDisplay.tsx',
|
||||
'packages/cli/src/ui/components/Composer.tsx',
|
||||
'packages/cli/src/ui/components/ContextUsageDisplay.tsx',
|
||||
'packages/cli/src/ui/components/DetailedMessagesDisplay.tsx',
|
||||
'packages/cli/src/ui/components/DialogManager.tsx',
|
||||
'packages/cli/src/ui/components/ExitPlanModeDialog.tsx',
|
||||
'packages/cli/src/ui/components/FolderTrustDialog.tsx',
|
||||
'packages/cli/src/ui/components/HooksDialog.tsx',
|
||||
'packages/cli/src/ui/components/IdeTrustChangeDialog.tsx',
|
||||
'packages/cli/src/ui/components/ModelStatsDisplay.tsx',
|
||||
'packages/cli/src/ui/components/MultiFolderTrustDialog.tsx',
|
||||
'packages/cli/src/ui/components/Notifications.tsx',
|
||||
'packages/cli/src/ui/components/QuotaDisplay.tsx',
|
||||
'packages/cli/src/ui/components/RewindViewer.tsx',
|
||||
'packages/cli/src/ui/components/SessionBrowser.tsx',
|
||||
'packages/cli/src/ui/components/SettingsDialog.tsx',
|
||||
'packages/cli/src/ui/components/ShowMoreLines.tsx',
|
||||
'packages/cli/src/ui/components/StatsDisplay.tsx',
|
||||
'packages/cli/src/ui/components/ThemeDialog.tsx',
|
||||
'packages/cli/src/ui/components/UserIdentity.tsx',
|
||||
'packages/cli/src/ui/components/messages/SubagentProgressDisplay.tsx',
|
||||
'packages/cli/src/ui/components/messages/Todo.tsx',
|
||||
'packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx',
|
||||
'packages/cli/src/ui/components/messages/ToolGroupMessage.tsx',
|
||||
'packages/cli/src/ui/components/shared/BaseSettingsDialog.tsx',
|
||||
'packages/cli/src/ui/components/shared/EnumSelector.tsx',
|
||||
'packages/cli/src/ui/components/shared/MaxSizedBox.tsx',
|
||||
'packages/cli/src/ui/components/shared/Scrollable.tsx',
|
||||
'packages/cli/src/ui/components/shared/VirtualizedList.tsx',
|
||||
'packages/cli/src/ui/components/shared/text-buffer.ts',
|
||||
'packages/cli/src/ui/components/triage/TriageDuplicates.tsx',
|
||||
'packages/cli/src/ui/components/triage/TriageIssues.tsx',
|
||||
'packages/cli/src/ui/components/views/McpStatus.tsx',
|
||||
'packages/cli/src/ui/contexts/KeypressContext.tsx',
|
||||
'packages/cli/src/ui/contexts/ScrollProvider.tsx',
|
||||
'packages/cli/src/ui/contexts/SessionContext.tsx',
|
||||
'packages/cli/src/ui/hooks/slashCommandProcessor.ts',
|
||||
'packages/cli/src/ui/hooks/useAtCompletion.ts',
|
||||
'packages/cli/src/ui/hooks/useCommandCompletion.tsx',
|
||||
'packages/cli/src/ui/hooks/useConsoleMessages.ts',
|
||||
'packages/cli/src/ui/hooks/useExtensionUpdates.ts',
|
||||
'packages/cli/src/ui/hooks/useGeminiStream.ts',
|
||||
'packages/cli/src/ui/hooks/useIncludeDirsTrust.tsx',
|
||||
'packages/cli/src/ui/hooks/useInputHistory.ts',
|
||||
'packages/cli/src/ui/hooks/useInputHistoryStore.ts',
|
||||
'packages/cli/src/ui/hooks/usePermissionsModifyTrust.ts',
|
||||
'packages/cli/src/ui/hooks/usePromptCompletion.ts',
|
||||
'packages/cli/src/ui/hooks/useQuotaAndFallback.ts',
|
||||
'packages/cli/src/ui/hooks/useSelectionList.ts',
|
||||
'packages/cli/src/ui/hooks/useShellCompletion.ts',
|
||||
'packages/cli/src/ui/hooks/useSlashCompletion.ts',
|
||||
'packages/cli/src/ui/hooks/useThemeCommand.ts',
|
||||
'packages/cli/src/ui/hooks/useToolScheduler.ts',
|
||||
'packages/cli/src/ui/hooks/vim.ts',
|
||||
'packages/cli/src/ui/themes/theme-manager.ts',
|
||||
'packages/cli/src/ui/utils/CodeColorizer.tsx',
|
||||
'packages/cli/src/ui/utils/MarkdownDisplay.tsx',
|
||||
'packages/cli/src/ui/utils/borderStyles.ts',
|
||||
'packages/cli/src/ui/utils/clipboardUtils.ts',
|
||||
'packages/cli/src/ui/utils/highlight.ts',
|
||||
'packages/cli/src/ui/utils/inlineThinkingMode.ts',
|
||||
'packages/cli/src/ui/utils/keybindingUtils.ts',
|
||||
'packages/cli/src/ui/utils/terminalCapabilityManager.ts',
|
||||
'packages/cli/src/ui/utils/terminalSetup.ts',
|
||||
'packages/cli/src/ui/utils/terminalUtils.ts',
|
||||
'packages/cli/src/utils/activityLogger.ts',
|
||||
'packages/cli/src/utils/commentJson.ts',
|
||||
'packages/cli/src/utils/deepMerge.ts',
|
||||
'packages/cli/src/utils/devtoolsService.ts',
|
||||
'packages/cli/src/utils/envVarResolver.ts',
|
||||
'packages/cli/src/utils/sandbox.ts',
|
||||
'packages/cli/src/utils/sessionUtils.ts',
|
||||
'packages/cli/src/utils/settingsUtils.ts',
|
||||
'packages/cli/src/zed-integration/zedIntegration.ts',
|
||||
'packages/core/src/agents/a2a-client-manager.ts',
|
||||
'packages/core/src/agents/a2aUtils.ts',
|
||||
'packages/core/src/agents/acknowledgedAgents.ts',
|
||||
'packages/core/src/agents/browser/browserManager.ts',
|
||||
'packages/core/src/agents/browser/mcpToolWrapper.ts',
|
||||
'packages/core/src/agents/local-executor.ts',
|
||||
'packages/core/src/agents/local-invocation.ts',
|
||||
'packages/core/src/agents/registry.ts',
|
||||
'packages/core/src/agents/subagent-tool.ts',
|
||||
'packages/core/src/availability/modelAvailabilityService.ts',
|
||||
'packages/core/src/availability/policyHelpers.ts',
|
||||
'packages/core/src/billing/billing.ts',
|
||||
'packages/core/src/code_assist/admin/mcpUtils.ts',
|
||||
'packages/core/src/code_assist/converter.ts',
|
||||
'packages/core/src/code_assist/oauth2.ts',
|
||||
'packages/core/src/code_assist/server.ts',
|
||||
'packages/core/src/code_assist/setup.ts',
|
||||
'packages/core/src/code_assist/telemetry.ts',
|
||||
'packages/core/src/commands/memory.ts',
|
||||
'packages/core/src/config/config.ts',
|
||||
'packages/core/src/confirmation-bus/message-bus.ts',
|
||||
'packages/core/src/core/baseLlmClient.ts',
|
||||
'packages/core/src/core/contentGenerator.ts',
|
||||
'packages/core/src/core/coreToolHookTriggers.ts',
|
||||
'packages/core/src/core/fakeContentGenerator.ts',
|
||||
'packages/core/src/core/geminiChat.ts',
|
||||
'packages/core/src/core/logger.ts',
|
||||
'packages/core/src/core/loggingContentGenerator.ts',
|
||||
'packages/core/src/core/turn.ts',
|
||||
'packages/core/src/hooks/hookRegistry.ts',
|
||||
'packages/core/src/hooks/hookRunner.ts',
|
||||
'packages/core/src/hooks/trustedHooks.ts',
|
||||
'packages/core/src/hooks/types.ts',
|
||||
'packages/core/src/ide/ide-client.ts',
|
||||
'packages/core/src/ide/ide-connection-utils.ts',
|
||||
'packages/core/src/ide/process-utils.ts',
|
||||
'packages/core/src/mcp/oauth-provider.ts',
|
||||
'packages/core/src/mcp/token-storage/base-token-storage.ts',
|
||||
'packages/core/src/policy/config.ts',
|
||||
'packages/core/src/prompts/mcp-prompts.ts',
|
||||
'packages/core/src/prompts/promptProvider.ts',
|
||||
'packages/core/src/routing/strategies/classifierStrategy.ts',
|
||||
'packages/core/src/routing/strategies/defaultStrategy.ts',
|
||||
'packages/core/src/routing/strategies/fallbackStrategy.ts',
|
||||
'packages/core/src/routing/strategies/numericalClassifierStrategy.ts',
|
||||
'packages/core/src/routing/strategies/overrideStrategy.ts',
|
||||
'packages/core/src/safety/checker-runner.ts',
|
||||
'packages/core/src/safety/conseca/conseca.ts',
|
||||
'packages/core/src/safety/conseca/policy-enforcer.ts',
|
||||
'packages/core/src/safety/conseca/policy-generator.ts',
|
||||
'packages/core/src/safety/context-builder.ts',
|
||||
'packages/core/src/scheduler/confirmation.ts',
|
||||
'packages/core/src/scheduler/scheduler.ts',
|
||||
'packages/core/src/scheduler/state-manager.ts',
|
||||
'packages/core/src/scheduler/tool-executor.ts',
|
||||
'packages/core/src/services/environmentSanitization.ts',
|
||||
'packages/core/src/services/modelConfigService.ts',
|
||||
'packages/core/src/services/sessionSummaryUtils.ts',
|
||||
'packages/core/src/services/shellExecutionService.ts',
|
||||
'packages/core/src/services/toolOutputMaskingService.ts',
|
||||
'packages/core/src/skills/skillLoader.ts',
|
||||
'packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts',
|
||||
'packages/core/src/telemetry/startupProfiler.ts',
|
||||
'packages/core/src/telemetry/telemetryAttributes.ts',
|
||||
'packages/core/src/telemetry/types.ts',
|
||||
'packages/core/src/telemetry/uiTelemetry.ts',
|
||||
'packages/core/src/tools/grep-utils.ts',
|
||||
'packages/core/src/tools/mcp-client.ts',
|
||||
'packages/core/src/tools/shell.ts',
|
||||
'packages/core/src/utils/bfsFileSearch.ts',
|
||||
'packages/core/src/utils/editCorrector.ts',
|
||||
'packages/core/src/utils/errors.ts',
|
||||
'packages/core/src/utils/fileDiffUtils.ts',
|
||||
'packages/core/src/utils/fileUtils.ts',
|
||||
'packages/core/src/utils/filesearch/crawler.ts',
|
||||
'packages/core/src/utils/filesearch/fileSearch.ts',
|
||||
'packages/core/src/utils/filesearch/result-cache.ts',
|
||||
'packages/core/src/utils/headless.ts',
|
||||
'packages/core/src/utils/ignoreFileParser.ts',
|
||||
'packages/core/src/utils/ignorePatterns.ts',
|
||||
'packages/core/src/utils/llm-edit-fixer.ts',
|
||||
'packages/core/src/utils/nextSpeakerChecker.ts',
|
||||
'packages/core/src/utils/partUtils.ts',
|
||||
'packages/core/src/utils/retry.ts',
|
||||
'packages/core/src/utils/safeJsonStringify.ts',
|
||||
'packages/core/src/utils/sessionUtils.ts',
|
||||
'packages/core/src/utils/shell-utils.ts',
|
||||
'packages/sdk/src/session.ts',
|
||||
],
|
||||
rules: {
|
||||
'@typescript-eslint/no-unnecessary-condition': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
// Legacy files with many @typescript-eslint/no-unnecessary-type-assertion issues
|
||||
files: ['packages/core/src/core/client.ts', 'packages/core/src/core/geminiChat.ts'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-unnecessary-type-assertion': 'off',
|
||||
},
|
||||
},
|
||||
// extra settings for scripts that we run directly with node
|
||||
{
|
||||
files: ['./integration-tests/**/*.js'],
|
||||
|
||||
@@ -55,7 +55,7 @@ describe.skip('ACP Environment and Auth', () => {
|
||||
|
||||
const bundlePath = join(import.meta.dirname, '..', 'bundle/gemini.js');
|
||||
|
||||
child = spawn('node', [bundlePath, '--experimental-acp'], {
|
||||
child = spawn('node', [bundlePath, '--acp'], {
|
||||
cwd: rig.homeDir!,
|
||||
stdio: ['pipe', 'pipe', 'inherit'],
|
||||
env: {
|
||||
@@ -120,7 +120,7 @@ describe.skip('ACP Environment and Auth', () => {
|
||||
|
||||
const bundlePath = join(import.meta.dirname, '..', 'bundle/gemini.js');
|
||||
|
||||
child = spawn('node', [bundlePath, '--experimental-acp'], {
|
||||
child = spawn('node', [bundlePath, '--acp'], {
|
||||
cwd: rig.homeDir!,
|
||||
stdio: ['pipe', 'pipe', 'inherit'],
|
||||
env: {
|
||||
|
||||
@@ -58,7 +58,7 @@ describe('ACP telemetry', () => {
|
||||
'node',
|
||||
[
|
||||
bundlePath,
|
||||
'--experimental-acp',
|
||||
'--acp',
|
||||
'--fake-responses',
|
||||
join(rig.testDir!, 'fake-responses.json'),
|
||||
],
|
||||
|
||||
@@ -104,7 +104,7 @@ describe('extension reloading', () => {
|
||||
return (
|
||||
output.includes(
|
||||
'test-server (from test-extension) - Ready (1 tool)',
|
||||
) && output.includes('- hello')
|
||||
) && output.includes('- mcp_test-server_hello')
|
||||
);
|
||||
},
|
||||
30000, // 30s timeout
|
||||
@@ -148,7 +148,7 @@ describe('extension reloading', () => {
|
||||
return (
|
||||
output.includes(
|
||||
'test-server (from test-extension) - Ready (1 tool)',
|
||||
) && output.includes('- goodbye')
|
||||
) && output.includes('- mcp_test-server_goodbye')
|
||||
);
|
||||
},
|
||||
30000,
|
||||
|
||||
Generated
+9
-9
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@google/gemini-cli",
|
||||
"version": "0.34.0-nightly.20260304.28af4e127",
|
||||
"version": "0.33.0-preview.9",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@google/gemini-cli",
|
||||
"version": "0.34.0-nightly.20260304.28af4e127",
|
||||
"version": "0.33.0-preview.9",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
@@ -17303,7 +17303,7 @@
|
||||
},
|
||||
"packages/a2a-server": {
|
||||
"name": "@google/gemini-cli-a2a-server",
|
||||
"version": "0.34.0-nightly.20260304.28af4e127",
|
||||
"version": "0.33.0-preview.9",
|
||||
"dependencies": {
|
||||
"@a2a-js/sdk": "^0.3.8",
|
||||
"@google-cloud/storage": "^7.16.0",
|
||||
@@ -17361,7 +17361,7 @@
|
||||
},
|
||||
"packages/cli": {
|
||||
"name": "@google/gemini-cli",
|
||||
"version": "0.34.0-nightly.20260304.28af4e127",
|
||||
"version": "0.33.0-preview.9",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "^0.12.0",
|
||||
@@ -17444,7 +17444,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@google/gemini-cli-core",
|
||||
"version": "0.34.0-nightly.20260304.28af4e127",
|
||||
"version": "0.33.0-preview.9",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@a2a-js/sdk": "^0.3.8",
|
||||
@@ -17709,7 +17709,7 @@
|
||||
},
|
||||
"packages/devtools": {
|
||||
"name": "@google/gemini-cli-devtools",
|
||||
"version": "0.34.0-nightly.20260304.28af4e127",
|
||||
"version": "0.33.0-preview.9",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"ws": "^8.16.0"
|
||||
@@ -17724,7 +17724,7 @@
|
||||
},
|
||||
"packages/sdk": {
|
||||
"name": "@google/gemini-cli-sdk",
|
||||
"version": "0.34.0-nightly.20260304.28af4e127",
|
||||
"version": "0.33.0-preview.9",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@google/gemini-cli-core": "file:../core",
|
||||
@@ -17741,7 +17741,7 @@
|
||||
},
|
||||
"packages/test-utils": {
|
||||
"name": "@google/gemini-cli-test-utils",
|
||||
"version": "0.34.0-nightly.20260304.28af4e127",
|
||||
"version": "0.33.0-preview.9",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@google/gemini-cli-core": "file:../core",
|
||||
@@ -17758,7 +17758,7 @@
|
||||
},
|
||||
"packages/vscode-ide-companion": {
|
||||
"name": "gemini-cli-vscode-ide-companion",
|
||||
"version": "0.34.0-nightly.20260304.28af4e127",
|
||||
"version": "0.33.0-preview.9",
|
||||
"license": "LICENSE",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.23.0",
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli",
|
||||
"version": "0.34.0-nightly.20260304.28af4e127",
|
||||
"version": "0.33.0-preview.9",
|
||||
"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.34.0-nightly.20260304.28af4e127"
|
||||
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.33.0-preview.9"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "cross-env NODE_ENV=development node scripts/start.js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli-a2a-server",
|
||||
"version": "0.34.0-nightly.20260304.28af4e127",
|
||||
"version": "0.33.0-preview.9",
|
||||
"description": "Gemini CLI A2A Server",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -793,7 +793,7 @@ export class Task {
|
||||
) {
|
||||
errorEvent = event;
|
||||
}
|
||||
const errorMessage = errorEvent?.value.error
|
||||
const errorMessage = errorEvent?.value?.error
|
||||
? getErrorMessage(errorEvent.value.error)
|
||||
: 'Unknown error from LLM stream';
|
||||
logger.error(
|
||||
@@ -802,7 +802,7 @@ export class Task {
|
||||
);
|
||||
|
||||
let errMessage = `Unknown error from LLM stream: ${JSON.stringify(event)}`;
|
||||
if (errorEvent?.value.error) {
|
||||
if (errorEvent?.value?.error) {
|
||||
errMessage = parseAndFormatApiError(errorEvent.value.error);
|
||||
}
|
||||
this.cancelPendingTools(`LLM stream error: ${errorMessage}`);
|
||||
|
||||
@@ -109,9 +109,9 @@ export async function loadConfig(
|
||||
};
|
||||
|
||||
const fileService = new FileDiscoveryService(workspaceDir, {
|
||||
respectGitIgnore: configParams.fileFiltering?.respectGitIgnore,
|
||||
respectGeminiIgnore: configParams.fileFiltering?.respectGeminiIgnore,
|
||||
customIgnoreFilePaths: configParams.fileFiltering?.customIgnoreFilePaths,
|
||||
respectGitIgnore: configParams?.fileFiltering?.respectGitIgnore,
|
||||
respectGeminiIgnore: configParams?.fileFiltering?.respectGeminiIgnore,
|
||||
customIgnoreFilePaths: configParams?.fileFiltering?.customIgnoreFilePaths,
|
||||
});
|
||||
const { memoryContent, fileCount, filePaths } =
|
||||
await loadServerHierarchicalMemory(
|
||||
@@ -212,7 +212,7 @@ export function loadEnvironment(): void {
|
||||
|
||||
function findEnvFile(startDir: string): string | null {
|
||||
let currentDir = path.resolve(startDir);
|
||||
for (;;) {
|
||||
while (true) {
|
||||
// prefer gemini-specific .env under GEMINI_DIR
|
||||
const geminiEnvPath = path.join(currentDir, GEMINI_DIR, '.env');
|
||||
if (fs.existsSync(geminiEnvPath)) {
|
||||
|
||||
@@ -148,7 +148,7 @@ function isPersistedStateMetadata(
|
||||
export function getPersistedState(
|
||||
metadata: PersistedTaskMetadata,
|
||||
): PersistedStateMetadata | undefined {
|
||||
const state = metadata[METADATA_KEY];
|
||||
const state = metadata?.[METADATA_KEY];
|
||||
if (isPersistedStateMetadata(state)) {
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ export function assertUniqueFinalEventIsLast(
|
||||
expect(finalEvent.metadata?.['coderAgent']).toMatchObject({
|
||||
kind: 'state-change',
|
||||
});
|
||||
expect(finalEvent.status.state).toBe('input-required');
|
||||
expect(finalEvent.status?.state).toBe('input-required');
|
||||
expect(finalEvent.final).toBe(true);
|
||||
|
||||
// There is only one event with final and its the last
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli",
|
||||
"version": "0.34.0-nightly.20260304.28af4e127",
|
||||
"version": "0.33.0-preview.9",
|
||||
"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.34.0-nightly.20260304.28af4e127"
|
||||
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.33.0-preview.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "^0.12.0",
|
||||
|
||||
+55
-3
@@ -14,7 +14,7 @@ import {
|
||||
type Mock,
|
||||
type Mocked,
|
||||
} from 'vitest';
|
||||
import { GeminiAgent, Session } from './zedIntegration.js';
|
||||
import { GeminiAgent, Session } from './acpClient.js';
|
||||
import type { CommandHandler } from './commandHandler.js';
|
||||
import * as acp from '@agentclientprotocol/sdk';
|
||||
import {
|
||||
@@ -208,7 +208,16 @@ describe('GeminiAgent', () => {
|
||||
});
|
||||
|
||||
expect(response.protocolVersion).toBe(acp.PROTOCOL_VERSION);
|
||||
expect(response.authMethods).toHaveLength(3);
|
||||
expect(response.authMethods).toHaveLength(4);
|
||||
const gatewayAuth = response.authMethods?.find(
|
||||
(m) => m.id === AuthType.GATEWAY,
|
||||
);
|
||||
expect(gatewayAuth?._meta).toEqual({
|
||||
gateway: {
|
||||
protocol: 'google',
|
||||
restartRequired: 'false',
|
||||
},
|
||||
});
|
||||
const geminiAuth = response.authMethods?.find(
|
||||
(m) => m.id === AuthType.USE_GEMINI,
|
||||
);
|
||||
@@ -228,6 +237,8 @@ describe('GeminiAgent', () => {
|
||||
expect(mockConfig.refreshAuth).toHaveBeenCalledWith(
|
||||
AuthType.LOGIN_WITH_GOOGLE,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
expect(mockSettings.setValue).toHaveBeenCalledWith(
|
||||
SettingScope.User,
|
||||
@@ -247,6 +258,8 @@ describe('GeminiAgent', () => {
|
||||
expect(mockConfig.refreshAuth).toHaveBeenCalledWith(
|
||||
AuthType.USE_GEMINI,
|
||||
'test-api-key',
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
expect(mockSettings.setValue).toHaveBeenCalledWith(
|
||||
SettingScope.User,
|
||||
@@ -255,6 +268,45 @@ describe('GeminiAgent', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should authenticate correctly with gateway method', async () => {
|
||||
await agent.authenticate({
|
||||
methodId: AuthType.GATEWAY,
|
||||
_meta: {
|
||||
gateway: {
|
||||
baseUrl: 'https://example.com',
|
||||
headers: { Authorization: 'Bearer token' },
|
||||
},
|
||||
},
|
||||
} as unknown as acp.AuthenticateRequest);
|
||||
|
||||
expect(mockConfig.refreshAuth).toHaveBeenCalledWith(
|
||||
AuthType.GATEWAY,
|
||||
undefined,
|
||||
'https://example.com',
|
||||
{ Authorization: 'Bearer token' },
|
||||
);
|
||||
expect(mockSettings.setValue).toHaveBeenCalledWith(
|
||||
SettingScope.User,
|
||||
'security.auth.selectedType',
|
||||
AuthType.GATEWAY,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw acp.RequestError when gateway payload is malformed', async () => {
|
||||
await expect(
|
||||
agent.authenticate({
|
||||
methodId: AuthType.GATEWAY,
|
||||
_meta: {
|
||||
gateway: {
|
||||
// Invalid baseUrl
|
||||
baseUrl: 123,
|
||||
headers: { Authorization: 'Bearer token' },
|
||||
},
|
||||
},
|
||||
} as unknown as acp.AuthenticateRequest),
|
||||
).rejects.toThrow(/Malformed gateway payload/);
|
||||
});
|
||||
|
||||
it('should create a new session', async () => {
|
||||
vi.useFakeTimers();
|
||||
mockConfig.getContentGeneratorConfig = vi.fn().mockReturnValue({
|
||||
@@ -1231,7 +1283,7 @@ describe('Session', () => {
|
||||
streamStarted(true);
|
||||
const reader = stream.getReader();
|
||||
try {
|
||||
for (;;) {
|
||||
while (true) {
|
||||
process.stdout.write('TEST: waiting for read\n');
|
||||
const { done, value } = await reader.read();
|
||||
process.stdout.write(`TEST: read returned done=${done}\n`);
|
||||
+60
-6
@@ -70,7 +70,7 @@ import { runExitCleanup } from '../utils/cleanup.js';
|
||||
import { SessionSelector } from '../utils/sessionUtils.js';
|
||||
|
||||
import { CommandHandler } from './commandHandler.js';
|
||||
export async function runZedIntegration(
|
||||
export async function runAcpClient(
|
||||
config: Config,
|
||||
settings: LoadedSettings,
|
||||
argv: CliArgs,
|
||||
@@ -98,6 +98,8 @@ export class GeminiAgent {
|
||||
private sessions: Map<string, Session> = new Map();
|
||||
private clientCapabilities: acp.ClientCapabilities | undefined;
|
||||
private apiKey: string | undefined;
|
||||
private baseUrl: string | undefined;
|
||||
private customHeaders: Record<string, string> | undefined;
|
||||
|
||||
constructor(
|
||||
private config: Config,
|
||||
@@ -131,6 +133,17 @@ export class GeminiAgent {
|
||||
name: 'Vertex AI',
|
||||
description: 'Use an API key with Vertex AI GenAI API',
|
||||
},
|
||||
{
|
||||
id: AuthType.GATEWAY,
|
||||
name: 'AI API Gateway',
|
||||
description: 'Use a custom AI API Gateway',
|
||||
_meta: {
|
||||
gateway: {
|
||||
protocol: 'google',
|
||||
restartRequired: 'false',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
await this.config.initialize();
|
||||
@@ -179,7 +192,38 @@ export class GeminiAgent {
|
||||
if (apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
}
|
||||
await this.config.refreshAuth(method, apiKey ?? this.apiKey);
|
||||
|
||||
// Extract gateway details if present
|
||||
const gatewaySchema = z.object({
|
||||
baseUrl: z.string().optional(),
|
||||
headers: z.record(z.string()).optional(),
|
||||
});
|
||||
|
||||
let baseUrl: string | undefined;
|
||||
let headers: Record<string, string> | undefined;
|
||||
|
||||
if (meta?.['gateway']) {
|
||||
const result = gatewaySchema.safeParse(meta['gateway']);
|
||||
if (result.success) {
|
||||
baseUrl = result.data.baseUrl;
|
||||
headers = result.data.headers;
|
||||
} else {
|
||||
throw new acp.RequestError(
|
||||
-32602,
|
||||
`Malformed gateway payload: ${result.error.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.baseUrl = baseUrl;
|
||||
this.customHeaders = headers;
|
||||
|
||||
await this.config.refreshAuth(
|
||||
method,
|
||||
apiKey ?? this.apiKey,
|
||||
baseUrl,
|
||||
headers,
|
||||
);
|
||||
} catch (e) {
|
||||
throw new acp.RequestError(-32000, getAcpErrorMessage(e));
|
||||
}
|
||||
@@ -209,7 +253,12 @@ export class GeminiAgent {
|
||||
let isAuthenticated = false;
|
||||
let authErrorMessage = '';
|
||||
try {
|
||||
await config.refreshAuth(authType, this.apiKey);
|
||||
await config.refreshAuth(
|
||||
authType,
|
||||
this.apiKey,
|
||||
this.baseUrl,
|
||||
this.customHeaders,
|
||||
);
|
||||
isAuthenticated = true;
|
||||
|
||||
// Extra validation for Gemini API key
|
||||
@@ -371,7 +420,12 @@ export class GeminiAgent {
|
||||
// This satisfies the security requirement to verify the user before executing
|
||||
// potentially unsafe server definitions.
|
||||
try {
|
||||
await config.refreshAuth(selectedAuthType, this.apiKey);
|
||||
await config.refreshAuth(
|
||||
selectedAuthType,
|
||||
this.apiKey,
|
||||
this.baseUrl,
|
||||
this.customHeaders,
|
||||
);
|
||||
} catch (e) {
|
||||
debugLogger.error(`Authentication failed: ${e}`);
|
||||
throw acp.RequestError.authRequired();
|
||||
@@ -657,11 +711,11 @@ export class Session {
|
||||
try {
|
||||
const model = resolveModel(
|
||||
this.config.getModel(),
|
||||
(await this.config.getGemini31Launched()) ?? false,
|
||||
(await this.config.getGemini31Launched?.()) ?? false,
|
||||
);
|
||||
const responseStream = await chat.sendMessageStream(
|
||||
{ model },
|
||||
nextMessage.parts ?? [],
|
||||
nextMessage?.parts ?? [],
|
||||
promptId,
|
||||
pendingSend.signal,
|
||||
LlmRole.MAIN,
|
||||
+1
-1
@@ -13,7 +13,7 @@ import {
|
||||
type Mocked,
|
||||
type Mock,
|
||||
} from 'vitest';
|
||||
import { GeminiAgent } from './zedIntegration.js';
|
||||
import { GeminiAgent } from './acpClient.js';
|
||||
import * as acp from '@agentclientprotocol/sdk';
|
||||
import {
|
||||
ApprovalMode,
|
||||
@@ -45,7 +45,7 @@ export const configureCommand: CommandModule<object, ConfigureArgs> = {
|
||||
const { name, setting, scope } = args;
|
||||
const settings = loadSettings(process.cwd()).merged;
|
||||
|
||||
if (!(settings.experimental.extensionConfig ?? true)) {
|
||||
if (!(settings.experimental?.extensionConfig ?? true)) {
|
||||
coreEvents.emitFeedback(
|
||||
'error',
|
||||
'Extension configuration is currently disabled. Enable it by setting "experimental.extensionConfig" to true.',
|
||||
|
||||
@@ -82,7 +82,7 @@ export async function handleUpdate(args: UpdateArgs) {
|
||||
extensionManager,
|
||||
updateState,
|
||||
() => {},
|
||||
settings.experimental.extensionReloading,
|
||||
settings.experimental?.extensionReloading,
|
||||
))!;
|
||||
if (
|
||||
updatedExtensionInfo.originalVersion !==
|
||||
|
||||
@@ -99,6 +99,12 @@ export async function configureSpecificSetting(
|
||||
const extensionConfig = await extensionManager.loadExtensionConfig(
|
||||
extension.path,
|
||||
);
|
||||
if (!extensionConfig) {
|
||||
logger.error(
|
||||
`Could not find configuration for extension "${extensionName}".`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await updateSetting(
|
||||
extensionConfig,
|
||||
@@ -131,7 +137,11 @@ export async function configureExtension(
|
||||
const extensionConfig = await extensionManager.loadExtensionConfig(
|
||||
extension.path,
|
||||
);
|
||||
if (!extensionConfig.settings || extensionConfig.settings.length === 0) {
|
||||
if (
|
||||
!extensionConfig ||
|
||||
!extensionConfig.settings ||
|
||||
extensionConfig.settings.length === 0
|
||||
) {
|
||||
logger.log(`Extension "${extensionName}" has no settings to configure.`);
|
||||
return;
|
||||
}
|
||||
@@ -165,7 +175,11 @@ export async function configureAllExtensions(
|
||||
const extensionConfig = await extensionManager.loadExtensionConfig(
|
||||
extension.path,
|
||||
);
|
||||
if (extensionConfig.settings && extensionConfig.settings.length > 0) {
|
||||
if (
|
||||
extensionConfig &&
|
||||
extensionConfig.settings &&
|
||||
extensionConfig.settings.length > 0
|
||||
) {
|
||||
logger.log(`\nConfiguring settings for "${extension.name}"...`);
|
||||
await configureExtensionSettings(
|
||||
extensionConfig,
|
||||
@@ -194,7 +208,7 @@ export async function configureExtensionSettings(
|
||||
process.cwd(),
|
||||
);
|
||||
|
||||
let workspaceSettings: Record<string, string | undefined> = {};
|
||||
let workspaceSettings: Record<string, string> = {};
|
||||
if (scope === ExtensionSettingScope.USER) {
|
||||
workspaceSettings = await getScopedEnvContents(
|
||||
extensionConfig,
|
||||
|
||||
@@ -236,7 +236,10 @@ export async function handleMigrateFromClaude() {
|
||||
const settings = loadSettings(workingDir);
|
||||
|
||||
// Merge migrated hooks with existing hooks
|
||||
const existingHooks = settings.merged.hooks as Record<string, unknown>;
|
||||
const existingHooks = (settings.merged?.hooks || {}) as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
const mergedHooks = { ...existingHooks, ...migratedHooks };
|
||||
|
||||
// Update settings (setValue automatically saves)
|
||||
|
||||
@@ -117,7 +117,7 @@ async function addMcpServer(
|
||||
const existingSettings = settings.forScope(settingsScope).settings;
|
||||
const mcpServers = existingSettings.mcpServers || {};
|
||||
|
||||
const isExistingServer = Object.hasOwn(mcpServers, name);
|
||||
const isExistingServer = !!mcpServers[name];
|
||||
if (isExistingServer) {
|
||||
debugLogger.log(
|
||||
`MCP server "${name}" is already configured within ${scope} settings.`,
|
||||
@@ -211,12 +211,11 @@ export const addCommand: CommandModule = {
|
||||
})
|
||||
.middleware((argv) => {
|
||||
// Handle -- separator args as server args if present
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
const dashArgs = argv['--'] as string[] | undefined;
|
||||
if (dashArgs && dashArgs.length > 0) {
|
||||
if (argv['--']) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
const existingArgs = argv['args'] as Array<string | number>;
|
||||
argv['args'] = [...existingArgs, ...dashArgs];
|
||||
const existingArgs = (argv['args'] as Array<string | number>) || [];
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
argv['args'] = [...existingArgs, ...(argv['--'] as string[])];
|
||||
}
|
||||
}),
|
||||
handler: async (argv) => {
|
||||
|
||||
@@ -43,9 +43,9 @@ async function handleEnable(args: Args): Promise<void> {
|
||||
}
|
||||
|
||||
const result = await canLoadServer(name, {
|
||||
adminMcpEnabled: settings.merged.admin.mcp.enabled,
|
||||
allowedList: settings.merged.mcp.allowed,
|
||||
excludedList: settings.merged.mcp.excluded,
|
||||
adminMcpEnabled: settings.merged.admin?.mcp?.enabled ?? true,
|
||||
allowedList: settings.merged.mcp?.allowed,
|
||||
excludedList: settings.merged.mcp?.excluded,
|
||||
});
|
||||
|
||||
if (
|
||||
|
||||
@@ -39,12 +39,10 @@ export async function getMcpServersFromConfig(
|
||||
requestSetting: promptForSetting,
|
||||
});
|
||||
const extensions = await extensionManager.loadExtensions();
|
||||
const mcpServers: Record<string, MCPServerConfig> = {
|
||||
...settings.mcpServers,
|
||||
};
|
||||
const mcpServers = { ...settings.mcpServers };
|
||||
for (const extension of extensions) {
|
||||
Object.entries(extension.mcpServers || {}).forEach(([key, server]) => {
|
||||
if (key in mcpServers) {
|
||||
if (mcpServers[key]) {
|
||||
return;
|
||||
}
|
||||
mcpServers[key] = {
|
||||
@@ -54,7 +52,7 @@ export async function getMcpServersFromConfig(
|
||||
});
|
||||
}
|
||||
|
||||
const adminAllowlist = settings.admin.mcp.config;
|
||||
const adminAllowlist = settings.admin?.mcp?.config;
|
||||
const filteredResult = applyAdminAllowlist(mcpServers, adminAllowlist);
|
||||
|
||||
return filteredResult;
|
||||
|
||||
@@ -24,7 +24,7 @@ async function removeMcpServer(
|
||||
const existingSettings = settings.forScope(settingsScope).settings;
|
||||
const mcpServers = existingSettings.mcpServers || {};
|
||||
|
||||
if (!(name in mcpServers)) {
|
||||
if (!mcpServers[name]) {
|
||||
debugLogger.log(`Server "${name}" not found in ${scope} settings.`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -81,7 +81,8 @@ export interface CliArgs {
|
||||
policy: string[] | undefined;
|
||||
allowedMcpServerNames: string[] | undefined;
|
||||
allowedTools: string[] | undefined;
|
||||
experimentalAcp: boolean | undefined;
|
||||
acp?: boolean;
|
||||
experimentalAcp?: boolean;
|
||||
extensions: string[] | undefined;
|
||||
listExtensions: boolean | undefined;
|
||||
resume: string | typeof RESUME_LATEST | undefined;
|
||||
@@ -177,10 +178,15 @@ export async function parseArguments(
|
||||
.filter(Boolean),
|
||||
),
|
||||
})
|
||||
.option('experimental-acp', {
|
||||
.option('acp', {
|
||||
type: 'boolean',
|
||||
description: 'Starts the agent in ACP mode',
|
||||
})
|
||||
.option('experimental-acp', {
|
||||
type: 'boolean',
|
||||
description:
|
||||
'Starts the agent in ACP mode (deprecated, use --acp instead)',
|
||||
})
|
||||
.option('allowed-mcp-server-names', {
|
||||
type: 'array',
|
||||
string: true,
|
||||
@@ -327,11 +333,11 @@ export async function parseArguments(
|
||||
return true;
|
||||
});
|
||||
|
||||
if (settings.experimental.extensionManagement) {
|
||||
if (settings.experimental?.extensionManagement) {
|
||||
yargsInstance.command(extensionsCommand);
|
||||
}
|
||||
|
||||
if (settings.skills.enabled) {
|
||||
if (settings.skills?.enabled ?? true) {
|
||||
yargsInstance.command(skillsCommand);
|
||||
}
|
||||
// Register hooks command if hooks are enabled
|
||||
@@ -456,16 +462,16 @@ export async function loadCliConfig(
|
||||
process.env['GEMINI_SANDBOX'] = 'true';
|
||||
}
|
||||
|
||||
const memoryImportFormat = settings.context.importFormat || 'tree';
|
||||
const includeDirectoryTree = settings.context.includeDirectoryTree;
|
||||
const memoryImportFormat = settings.context?.importFormat || 'tree';
|
||||
const includeDirectoryTree = settings.context?.includeDirectoryTree ?? true;
|
||||
|
||||
const ideMode = settings.ide.enabled;
|
||||
const ideMode = settings.ide?.enabled ?? false;
|
||||
|
||||
const folderTrust =
|
||||
process.env['GEMINI_CLI_INTEGRATION_TEST'] === 'true' ||
|
||||
process.env['VITEST'] === 'true'
|
||||
? false
|
||||
: settings.security.folderTrust.enabled;
|
||||
: (settings.security?.folderTrust?.enabled ?? false);
|
||||
const trustedFolder =
|
||||
isWorkspaceTrusted(settings, cwd, undefined, {
|
||||
prompt: argv.prompt,
|
||||
@@ -476,7 +482,7 @@ export async function loadCliConfig(
|
||||
// TODO(b/343434939): This is a bit of a hack. The contextFileName should ideally be passed
|
||||
// directly to the Config constructor in core, and have core handle setGeminiMdFilename.
|
||||
// However, loadHierarchicalGeminiMemory is called *before* createServerConfig.
|
||||
if (settings.context.fileName) {
|
||||
if (settings.context?.fileName) {
|
||||
setServerGeminiMdFilename(settings.context.fileName);
|
||||
} else {
|
||||
// Reset to default if not provided in settings.
|
||||
@@ -487,15 +493,15 @@ export async function loadCliConfig(
|
||||
|
||||
const memoryFileFiltering = {
|
||||
...DEFAULT_MEMORY_FILE_FILTERING_OPTIONS,
|
||||
...settings.context.fileFiltering,
|
||||
...settings.context?.fileFiltering,
|
||||
};
|
||||
|
||||
const fileFiltering = {
|
||||
...DEFAULT_FILE_FILTERING_OPTIONS,
|
||||
...settings.context.fileFiltering,
|
||||
...settings.context?.fileFiltering,
|
||||
};
|
||||
|
||||
const includeDirectories = settings.context.includeDirectories
|
||||
const includeDirectories = (settings.context?.includeDirectories || [])
|
||||
.map(resolvePath)
|
||||
.concat((argv.includeDirectories || []).map(resolvePath));
|
||||
|
||||
@@ -515,7 +521,7 @@ export async function loadCliConfig(
|
||||
.getExtensions()
|
||||
.find((ext) => ext.isActive && ext.plan?.directory)?.plan;
|
||||
|
||||
const experimentalJitContext = settings.experimental.jitContext;
|
||||
const experimentalJitContext = settings.experimental?.jitContext ?? false;
|
||||
|
||||
let memoryContent: string | HierarchicalMemory = '';
|
||||
let fileCount = 0;
|
||||
@@ -525,7 +531,7 @@ export async function loadCliConfig(
|
||||
// Call the (now wrapper) loadHierarchicalGeminiMemory which calls the server's version
|
||||
const result = await loadServerHierarchicalMemory(
|
||||
cwd,
|
||||
settings.context.loadMemoryFromIncludeDirectories
|
||||
settings.context?.loadMemoryFromIncludeDirectories || false
|
||||
? includeDirectories
|
||||
: [],
|
||||
debugMode,
|
||||
@@ -534,7 +540,7 @@ export async function loadCliConfig(
|
||||
trustedFolder,
|
||||
memoryImportFormat,
|
||||
memoryFileFiltering,
|
||||
settings.context.discoveryMaxDirs,
|
||||
settings.context?.discoveryMaxDirs,
|
||||
);
|
||||
memoryContent = result.memoryContent;
|
||||
fileCount = result.fileCount;
|
||||
@@ -548,8 +554,8 @@ export async function loadCliConfig(
|
||||
const rawApprovalMode =
|
||||
argv.approvalMode ||
|
||||
(argv.yolo ? 'yolo' : undefined) ||
|
||||
((settings.general.defaultApprovalMode as string) !== 'yolo'
|
||||
? settings.general.defaultApprovalMode
|
||||
((settings.general?.defaultApprovalMode as string) !== 'yolo'
|
||||
? settings.general?.defaultApprovalMode
|
||||
: undefined);
|
||||
|
||||
if (rawApprovalMode) {
|
||||
@@ -561,7 +567,7 @@ export async function loadCliConfig(
|
||||
approvalMode = ApprovalMode.AUTO_EDIT;
|
||||
break;
|
||||
case 'plan':
|
||||
if (!settings.experimental.plan) {
|
||||
if (!(settings.experimental?.plan ?? false)) {
|
||||
debugLogger.warn(
|
||||
'Approval mode "plan" is only available when experimental.plan is enabled. Falling back to "default".',
|
||||
);
|
||||
@@ -583,9 +589,9 @@ export async function loadCliConfig(
|
||||
}
|
||||
|
||||
// Override approval mode if disableYoloMode is set.
|
||||
if (settings.security.disableYoloMode || settings.admin.secureModeEnabled) {
|
||||
if (settings.security?.disableYoloMode || settings.admin?.secureModeEnabled) {
|
||||
if (approvalMode === ApprovalMode.YOLO) {
|
||||
if (settings.admin.secureModeEnabled) {
|
||||
if (settings.admin?.secureModeEnabled) {
|
||||
debugLogger.error(
|
||||
'YOLO mode is disabled by "secureModeEnabled" setting.',
|
||||
);
|
||||
@@ -632,11 +638,12 @@ export async function loadCliConfig(
|
||||
// -i/--prompt-interactive forces interactive mode with an initial prompt
|
||||
const interactive =
|
||||
!!argv.promptInteractive ||
|
||||
!!argv.acp ||
|
||||
!!argv.experimentalAcp ||
|
||||
(!isHeadlessMode({ prompt: argv.prompt, query: argv.query }) &&
|
||||
!argv.isCommand);
|
||||
|
||||
const allowedTools = argv.allowedTools || settings.tools.allowed || [];
|
||||
const allowedTools = argv.allowedTools || settings.tools?.allowed || [];
|
||||
const allowedToolsSet = new Set(allowedTools);
|
||||
|
||||
// In non-interactive mode, exclude tools that require a prompt.
|
||||
@@ -694,7 +701,7 @@ export async function loadCliConfig(
|
||||
},
|
||||
mcp: {
|
||||
...settings.mcp,
|
||||
allowed: argv.allowedMcpServerNames ?? settings.mcp.allowed,
|
||||
allowed: argv.allowedMcpServerNames ?? settings.mcp?.allowed,
|
||||
},
|
||||
policyPaths: argv.policy,
|
||||
};
|
||||
@@ -715,7 +722,7 @@ export async function loadCliConfig(
|
||||
|
||||
const defaultModel = PREVIEW_GEMINI_MODEL_AUTO;
|
||||
const specifiedModel =
|
||||
argv.model || process.env['GEMINI_MODEL'] || settings.model.name;
|
||||
argv.model || process.env['GEMINI_MODEL'] || settings.model?.name;
|
||||
|
||||
const resolvedModel =
|
||||
specifiedModel === GEMINI_MODEL_ALIAS_AUTO
|
||||
@@ -729,9 +736,9 @@ export async function loadCliConfig(
|
||||
|
||||
const ptyInfo = await getPty();
|
||||
|
||||
const mcpEnabled = settings.admin.mcp.enabled;
|
||||
const extensionsEnabled = settings.admin.extensions.enabled;
|
||||
const adminSkillsEnabled = settings.admin.skills.enabled;
|
||||
const mcpEnabled = settings.admin?.mcp?.enabled ?? true;
|
||||
const extensionsEnabled = settings.admin?.extensions?.enabled ?? true;
|
||||
const adminSkillsEnabled = settings.admin?.skills?.enabled ?? true;
|
||||
|
||||
// Create MCP enablement manager and callbacks
|
||||
const mcpEnablementManager = McpServerEnablementManager.getInstance();
|
||||
@@ -739,8 +746,8 @@ export async function loadCliConfig(
|
||||
? mcpEnablementManager.getEnablementCallbacks()
|
||||
: undefined;
|
||||
|
||||
const adminAllowlist = settings.admin.mcp.config;
|
||||
let mcpServerCommand = mcpEnabled ? settings.mcp.serverCommand : undefined;
|
||||
const adminAllowlist = settings.admin?.mcp?.config;
|
||||
let mcpServerCommand = mcpEnabled ? settings.mcp?.serverCommand : undefined;
|
||||
let mcpServers = mcpEnabled ? settings.mcpServers : {};
|
||||
|
||||
if (mcpEnabled && adminAllowlist && Object.keys(adminAllowlist).length > 0) {
|
||||
@@ -748,7 +755,7 @@ export async function loadCliConfig(
|
||||
mcpServers = result.mcpServers;
|
||||
mcpServerCommand = undefined;
|
||||
|
||||
if (result.blockedServerNames.length > 0) {
|
||||
if (result.blockedServerNames && result.blockedServerNames.length > 0) {
|
||||
const message = getAdminBlockedMcpServersMessage(
|
||||
result.blockedServerNames,
|
||||
undefined,
|
||||
@@ -758,6 +765,7 @@ export async function loadCliConfig(
|
||||
}
|
||||
|
||||
return new Config({
|
||||
acpMode: !!argv.acp || !!argv.experimentalAcp,
|
||||
sessionId,
|
||||
clientVersion: await getVersion(),
|
||||
embeddingModel: DEFAULT_GEMINI_EMBEDDING_MODEL,
|
||||
@@ -766,17 +774,17 @@ export async function loadCliConfig(
|
||||
includeDirectoryTree,
|
||||
includeDirectories,
|
||||
loadMemoryFromIncludeDirectories:
|
||||
settings.context.loadMemoryFromIncludeDirectories,
|
||||
settings.context?.loadMemoryFromIncludeDirectories || false,
|
||||
debugMode,
|
||||
question,
|
||||
|
||||
coreTools: settings.tools.core || undefined,
|
||||
coreTools: settings.tools?.core || undefined,
|
||||
allowedTools: allowedTools.length > 0 ? allowedTools : undefined,
|
||||
policyEngineConfig,
|
||||
policyUpdateConfirmationRequest,
|
||||
excludeTools,
|
||||
toolDiscoveryCommand: settings.tools.discoveryCommand,
|
||||
toolCallCommand: settings.tools.callCommand,
|
||||
toolDiscoveryCommand: settings.tools?.discoveryCommand,
|
||||
toolCallCommand: settings.tools?.callCommand,
|
||||
mcpServerCommand,
|
||||
mcpServers,
|
||||
mcpEnablementCallbacks,
|
||||
@@ -785,32 +793,32 @@ export async function loadCliConfig(
|
||||
agents: settings.agents,
|
||||
adminSkillsEnabled,
|
||||
allowedMcpServers: mcpEnabled
|
||||
? (argv.allowedMcpServerNames ?? settings.mcp.allowed)
|
||||
? (argv.allowedMcpServerNames ?? settings.mcp?.allowed)
|
||||
: undefined,
|
||||
blockedMcpServers: mcpEnabled
|
||||
? argv.allowedMcpServerNames
|
||||
? undefined
|
||||
: settings.mcp.excluded
|
||||
: settings.mcp?.excluded
|
||||
: undefined,
|
||||
blockedEnvironmentVariables:
|
||||
settings.security.environmentVariableRedaction.blocked,
|
||||
settings.security?.environmentVariableRedaction?.blocked,
|
||||
enableEnvironmentVariableRedaction:
|
||||
settings.security.environmentVariableRedaction.enabled,
|
||||
settings.security?.environmentVariableRedaction?.enabled,
|
||||
userMemory: memoryContent,
|
||||
geminiMdFileCount: fileCount,
|
||||
geminiMdFilePaths: filePaths,
|
||||
approvalMode,
|
||||
disableYoloMode:
|
||||
settings.security.disableYoloMode || settings.admin.secureModeEnabled,
|
||||
showMemoryUsage: settings.ui.showMemoryUsage,
|
||||
settings.security?.disableYoloMode || settings.admin?.secureModeEnabled,
|
||||
showMemoryUsage: settings.ui?.showMemoryUsage || false,
|
||||
accessibility: {
|
||||
...settings.ui.accessibility,
|
||||
...settings.ui?.accessibility,
|
||||
screenReader,
|
||||
},
|
||||
telemetry: telemetrySettings,
|
||||
usageStatisticsEnabled: settings.privacy.usageStatisticsEnabled,
|
||||
usageStatisticsEnabled: settings.privacy?.usageStatisticsEnabled,
|
||||
fileFiltering,
|
||||
checkpointing: settings.general.checkpointing.enabled,
|
||||
checkpointing: settings.general?.checkpointing?.enabled,
|
||||
proxy:
|
||||
process.env['HTTPS_PROXY'] ||
|
||||
process.env['https_proxy'] ||
|
||||
@@ -821,7 +829,7 @@ export async function loadCliConfig(
|
||||
bugCommand: settings.advanced?.bugCommand,
|
||||
model: resolvedModel,
|
||||
maxSessionTurns: settings.model?.maxSessionTurns,
|
||||
experimentalZedIntegration: argv.experimentalAcp || false,
|
||||
|
||||
listExtensions: argv.listExtensions || false,
|
||||
listSessions: argv.listSessions || false,
|
||||
deleteSession: argv.deleteSession,
|
||||
@@ -830,7 +838,6 @@ export async function loadCliConfig(
|
||||
enableExtensionReloading: settings.experimental?.extensionReloading,
|
||||
enableAgents: settings.experimental?.enableAgents,
|
||||
plan: settings.experimental?.plan,
|
||||
tracker: settings.experimental?.taskTracker,
|
||||
directWebFetch: settings.experimental?.directWebFetch,
|
||||
planSettings: settings.general?.plan?.directory
|
||||
? settings.general.plan
|
||||
@@ -868,6 +875,7 @@ export async function loadCliConfig(
|
||||
fakeResponses: argv.fakeResponses,
|
||||
recordResponses: argv.recordResponses,
|
||||
retryFetchErrors: settings.general?.retryFetchErrors,
|
||||
billing: settings.billing,
|
||||
maxAttempts: settings.general?.maxAttempts,
|
||||
ptyInfo: ptyInfo?.name,
|
||||
disableLLMCorrection: settings.tools?.disableLLMCorrection,
|
||||
|
||||
@@ -154,8 +154,11 @@ export class ExtensionManager extends ExtensionLoader {
|
||||
installMetadata: ExtensionInstallMetadata,
|
||||
previousExtensionConfig?: ExtensionConfig,
|
||||
): Promise<GeminiCLIExtension> {
|
||||
if (this.settings.security.allowedExtensions.length > 0) {
|
||||
const extensionAllowed = this.settings.security.allowedExtensions.some(
|
||||
if (
|
||||
this.settings.security?.allowedExtensions &&
|
||||
this.settings.security?.allowedExtensions.length > 0
|
||||
) {
|
||||
const extensionAllowed = this.settings.security?.allowedExtensions.some(
|
||||
(pattern) => {
|
||||
try {
|
||||
return new RegExp(pattern).test(installMetadata.source);
|
||||
@@ -309,7 +312,7 @@ Would you like to attempt to install via "git clone" instead?`,
|
||||
const destinationPath = new ExtensionStorage(
|
||||
newExtensionName,
|
||||
).getExtensionDir();
|
||||
let previousSettings: Record<string, string | undefined> | undefined;
|
||||
let previousSettings: Record<string, string> | undefined;
|
||||
if (isUpdate) {
|
||||
previousSettings = await getEnvContents(
|
||||
previousExtensionConfig,
|
||||
@@ -623,16 +626,19 @@ Would you like to attempt to install via "git clone" instead?`,
|
||||
|
||||
const installMetadata = loadInstallMetadata(extensionDir);
|
||||
let effectiveExtensionPath = extensionDir;
|
||||
if (this.settings.security.allowedExtensions.length > 0) {
|
||||
if (
|
||||
this.settings.security?.allowedExtensions &&
|
||||
this.settings.security?.allowedExtensions.length > 0
|
||||
) {
|
||||
if (!installMetadata?.source) {
|
||||
throw new Error(
|
||||
`Failed to load extension ${extensionDir}. The ${INSTALL_METADATA_FILENAME} file is missing or misconfigured.`,
|
||||
);
|
||||
}
|
||||
const extensionAllowed = this.settings.security.allowedExtensions.some(
|
||||
const extensionAllowed = this.settings.security?.allowedExtensions.some(
|
||||
(pattern) => {
|
||||
try {
|
||||
return new RegExp(pattern).test(installMetadata.source);
|
||||
return new RegExp(pattern).test(installMetadata?.source);
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Invalid regex pattern in allowedExtensions setting: "${pattern}. Error: ${getErrorMessage(e)}`,
|
||||
@@ -666,8 +672,8 @@ Would you like to attempt to install via "git clone" instead?`,
|
||||
|
||||
const extensionId = getExtensionId(config, installMetadata);
|
||||
|
||||
let userSettings: Record<string, string | undefined> = {};
|
||||
let workspaceSettings: Record<string, string | undefined> = {};
|
||||
let userSettings: Record<string, string> = {};
|
||||
let workspaceSettings: Record<string, string> = {};
|
||||
|
||||
if (this.settings.experimental.extensionConfig) {
|
||||
userSettings = await getScopedEnvContents(
|
||||
|
||||
+3
-7
@@ -10,15 +10,11 @@
|
||||
<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="117" lengthAdjust="spacingAndGlyphs" font-weight="bold">Agent Skills:</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="36" lengthAdjust="spacingAndGlyphs"> * </text>
|
||||
<text x="36" y="189" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs" font-weight="bold">skill1</text>
|
||||
<text x="90" y="189" fill="#ffffff" textLength="810" lengthAdjust="spacingAndGlyphs">: desc1 </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="36" lengthAdjust="spacingAndGlyphs"> * </text>
|
||||
<text x="36" y="240" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs" font-weight="bold">skill2</text>
|
||||
<text x="90" y="240" fill="#ffffff" textLength="810" lengthAdjust="spacingAndGlyphs">: desc2 </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>
|
||||
|
||||
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.0 KiB |
+2
-4
@@ -5,11 +5,9 @@
|
||||
<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 "test-ext". </text>
|
||||
<text x="0" y="36" fill="#ffffff" textLength="117" lengthAdjust="spacingAndGlyphs" font-weight="bold">Agent Skills:</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="36" lengthAdjust="spacingAndGlyphs"> * </text>
|
||||
<text x="36" y="104" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs" font-weight="bold">locked-skill</text>
|
||||
<text x="144" y="104" fill="#ffffff" textLength="756" lengthAdjust="spacingAndGlyphs">: A skill in a locked dir </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>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.8 KiB |
+1
-3
@@ -6,9 +6,7 @@
|
||||
<g transform="translate(10, 10)">
|
||||
<text x="0" y="2" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs">Installing agent skill(s) from "https://example.com/repo.git". </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="36" lengthAdjust="spacingAndGlyphs"> * </text>
|
||||
<text x="36" y="70" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs" font-weight="bold">skill1</text>
|
||||
<text x="90" y="70" fill="#ffffff" textLength="810" lengthAdjust="spacingAndGlyphs">: desc1 </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's system</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.0 KiB |
@@ -166,7 +166,7 @@ export class ExtensionEnablementManager {
|
||||
const extensionConfig = config[extensionName];
|
||||
// Extensions are enabled by default.
|
||||
let enabled = true;
|
||||
const allOverrides = extensionConfig.overrides ?? [];
|
||||
const allOverrides = extensionConfig?.overrides ?? [];
|
||||
for (const rule of allOverrides) {
|
||||
const override = Override.fromFileRule(rule);
|
||||
if (override.matchesPath(ensureLeadingAndTrailingSlash(currentPath))) {
|
||||
|
||||
@@ -64,7 +64,7 @@ export async function maybePromptForSettings(
|
||||
extensionId: string,
|
||||
requestSetting: (setting: ExtensionSetting) => Promise<string>,
|
||||
previousExtensionConfig?: ExtensionConfig,
|
||||
previousSettings?: Record<string, string | undefined>,
|
||||
previousSettings?: Record<string, string>,
|
||||
): Promise<void> {
|
||||
const { name: extensionName, settings } = extensionConfig;
|
||||
if (
|
||||
@@ -92,9 +92,7 @@ export async function maybePromptForSettings(
|
||||
previousExtensionConfig?.settings ?? [],
|
||||
);
|
||||
|
||||
const allSettings: Record<string, string | undefined> = {
|
||||
...previousSettings,
|
||||
};
|
||||
const allSettings: Record<string, string> = { ...previousSettings };
|
||||
|
||||
for (const removedEnvSetting of settingsChanges.removeEnv) {
|
||||
delete allSettings[removedEnvSetting.envVar];
|
||||
@@ -176,13 +174,13 @@ export async function getScopedEnvContents(
|
||||
extensionId: string,
|
||||
scope: ExtensionSettingScope,
|
||||
workspaceDir?: string,
|
||||
): Promise<Record<string, string | undefined>> {
|
||||
): Promise<Record<string, string>> {
|
||||
const { name: extensionName } = extensionConfig;
|
||||
const keychain = new KeychainTokenStorage(
|
||||
getKeychainStorageName(extensionName, extensionId, scope, workspaceDir),
|
||||
);
|
||||
const envFilePath = getEnvFilePath(extensionName, scope, workspaceDir);
|
||||
let customEnv: Record<string, string | undefined> = {};
|
||||
let customEnv: Record<string, string> = {};
|
||||
if (fsSync.existsSync(envFilePath)) {
|
||||
const stat = fsSync.statSync(envFilePath);
|
||||
if (!stat.isDirectory()) {
|
||||
@@ -208,7 +206,7 @@ export async function getEnvContents(
|
||||
extensionConfig: ExtensionConfig,
|
||||
extensionId: string,
|
||||
workspaceDir: string,
|
||||
): Promise<Record<string, string | undefined>> {
|
||||
): Promise<Record<string, string>> {
|
||||
if (!extensionConfig.settings || extensionConfig.settings.length === 0) {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
|
||||
@@ -109,16 +109,16 @@ export function tryParseGithubUrl(source: string): GithubRepoInfo | null {
|
||||
if (!parsedUrl) {
|
||||
throw new Error(`Invalid repo URL: ${source}`);
|
||||
}
|
||||
if (parsedUrl.host !== 'github.com') {
|
||||
if (parsedUrl?.host !== 'github.com') {
|
||||
return null;
|
||||
}
|
||||
// The pathname should be "/owner/repo".
|
||||
const parts = parsedUrl.pathname
|
||||
const parts = parsedUrl?.pathname
|
||||
.split('/')
|
||||
// Remove the empty segments, fixes trailing and leading slashes
|
||||
.filter((part) => part !== '');
|
||||
|
||||
if (parts.length !== 2) {
|
||||
if (parts?.length !== 2) {
|
||||
throw new Error(
|
||||
`Invalid GitHub repository source: ${source}. Expected "owner/repo" or a github repo uri.`,
|
||||
);
|
||||
|
||||
@@ -48,7 +48,7 @@ export async function updateExtension(
|
||||
`Extension ${extension.name} cannot be updated, type is unknown.`,
|
||||
);
|
||||
}
|
||||
if (installMetadata.type === 'link') {
|
||||
if (installMetadata?.type === 'link') {
|
||||
dispatchExtensionStateUpdate({
|
||||
type: 'SET_STATE',
|
||||
payload: { name: extension.name, state: ExtensionUpdateState.UP_TO_DATE },
|
||||
|
||||
@@ -226,7 +226,7 @@ export class McpServerEnablementManager {
|
||||
async isFileEnabled(serverName: string): Promise<boolean> {
|
||||
const config = await this.readConfig();
|
||||
const state = config[normalizeServerId(serverName)];
|
||||
return state.enabled ?? true;
|
||||
return state?.enabled ?? true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -89,13 +89,13 @@ describe('Policy Engine Integration Tests', () => {
|
||||
// Tools from allowed server should be allowed
|
||||
// Tools from allowed server should be allowed
|
||||
expect(
|
||||
(await engine.check({ name: 'allowed-server__tool1' }, undefined))
|
||||
(await engine.check({ name: 'mcp_allowed-server_tool1' }, undefined))
|
||||
.decision,
|
||||
).toBe(PolicyDecision.ALLOW);
|
||||
expect(
|
||||
(
|
||||
await engine.check(
|
||||
{ name: 'allowed-server__another_tool' },
|
||||
{ name: 'mcp_allowed-server_another_tool' },
|
||||
undefined,
|
||||
)
|
||||
).decision,
|
||||
@@ -103,13 +103,13 @@ describe('Policy Engine Integration Tests', () => {
|
||||
|
||||
// Tools from trusted server should be allowed
|
||||
expect(
|
||||
(await engine.check({ name: 'trusted-server__tool1' }, undefined))
|
||||
(await engine.check({ name: 'mcp_trusted-server_tool1' }, undefined))
|
||||
.decision,
|
||||
).toBe(PolicyDecision.ALLOW);
|
||||
expect(
|
||||
(
|
||||
await engine.check(
|
||||
{ name: 'trusted-server__special_tool' },
|
||||
{ name: 'mcp_trusted-server_special_tool' },
|
||||
undefined,
|
||||
)
|
||||
).decision,
|
||||
@@ -117,17 +117,17 @@ describe('Policy Engine Integration Tests', () => {
|
||||
|
||||
// Tools from blocked server should be denied
|
||||
expect(
|
||||
(await engine.check({ name: 'blocked-server__tool1' }, undefined))
|
||||
(await engine.check({ name: 'mcp_blocked-server_tool1' }, undefined))
|
||||
.decision,
|
||||
).toBe(PolicyDecision.DENY);
|
||||
expect(
|
||||
(await engine.check({ name: 'blocked-server__any_tool' }, undefined))
|
||||
(await engine.check({ name: 'mcp_blocked-server_any_tool' }, undefined))
|
||||
.decision,
|
||||
).toBe(PolicyDecision.DENY);
|
||||
|
||||
// Tools from unknown servers should use default
|
||||
expect(
|
||||
(await engine.check({ name: 'unknown-server__tool' }, undefined))
|
||||
(await engine.check({ name: 'mcp_unknown-server_tool' }, undefined))
|
||||
.decision,
|
||||
).toBe(PolicyDecision.ASK_USER);
|
||||
});
|
||||
@@ -147,12 +147,16 @@ describe('Policy Engine Integration Tests', () => {
|
||||
|
||||
// ANY tool with a server name should be allowed
|
||||
expect(
|
||||
(await engine.check({ name: 'mcp-server__tool' }, 'mcp-server'))
|
||||
(await engine.check({ name: 'mcp_mcp-server_tool' }, 'mcp-server'))
|
||||
.decision,
|
||||
).toBe(PolicyDecision.ALLOW);
|
||||
expect(
|
||||
(await engine.check({ name: 'another-server__tool' }, 'another-server'))
|
||||
.decision,
|
||||
(
|
||||
await engine.check(
|
||||
{ name: 'mcp_another-server_tool' },
|
||||
'another-server',
|
||||
)
|
||||
).decision,
|
||||
).toBe(PolicyDecision.ALLOW);
|
||||
|
||||
// Built-in tools should NOT be allowed by the MCP wildcard
|
||||
@@ -167,7 +171,7 @@ describe('Policy Engine Integration Tests', () => {
|
||||
allowed: ['my-server'],
|
||||
},
|
||||
tools: {
|
||||
exclude: ['my-server__dangerous-tool'],
|
||||
exclude: ['mcp_my-server_dangerous-tool'],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -180,20 +184,24 @@ describe('Policy Engine Integration Tests', () => {
|
||||
// MCP server allowed (priority 4.1) provides general allow for server
|
||||
// MCP server allowed (priority 4.1) provides general allow for server
|
||||
expect(
|
||||
(await engine.check({ name: 'my-server__safe-tool' }, undefined))
|
||||
(await engine.check({ name: 'mcp_my-server_safe-tool' }, undefined))
|
||||
.decision,
|
||||
).toBe(PolicyDecision.ALLOW);
|
||||
// But specific tool exclude (priority 4.4) wins over server allow
|
||||
expect(
|
||||
(await engine.check({ name: 'my-server__dangerous-tool' }, undefined))
|
||||
.decision,
|
||||
(
|
||||
await engine.check(
|
||||
{ name: 'mcp_my-server_dangerous-tool' },
|
||||
undefined,
|
||||
)
|
||||
).decision,
|
||||
).toBe(PolicyDecision.DENY);
|
||||
});
|
||||
|
||||
it('should handle complex mixed configurations', async () => {
|
||||
const settings: Settings = {
|
||||
tools: {
|
||||
allowed: ['custom-tool', 'my-server__special-tool'],
|
||||
allowed: ['custom-tool', 'mcp_my-server_special-tool'],
|
||||
exclude: ['glob', 'dangerous-tool'],
|
||||
},
|
||||
mcp: {
|
||||
@@ -238,21 +246,21 @@ describe('Policy Engine Integration Tests', () => {
|
||||
(await engine.check({ name: 'custom-tool' }, undefined)).decision,
|
||||
).toBe(PolicyDecision.ALLOW);
|
||||
expect(
|
||||
(await engine.check({ name: 'my-server__special-tool' }, undefined))
|
||||
(await engine.check({ name: 'mcp_my-server_special-tool' }, undefined))
|
||||
.decision,
|
||||
).toBe(PolicyDecision.ALLOW);
|
||||
|
||||
// MCP server tools
|
||||
expect(
|
||||
(await engine.check({ name: 'allowed-server__tool' }, undefined))
|
||||
(await engine.check({ name: 'mcp_allowed-server_tool' }, undefined))
|
||||
.decision,
|
||||
).toBe(PolicyDecision.ALLOW);
|
||||
expect(
|
||||
(await engine.check({ name: 'trusted-server__tool' }, undefined))
|
||||
(await engine.check({ name: 'mcp_trusted-server_tool' }, undefined))
|
||||
.decision,
|
||||
).toBe(PolicyDecision.ALLOW);
|
||||
expect(
|
||||
(await engine.check({ name: 'blocked-server__tool' }, undefined))
|
||||
(await engine.check({ name: 'mcp_blocked-server_tool' }, undefined))
|
||||
.decision,
|
||||
).toBe(PolicyDecision.DENY);
|
||||
|
||||
@@ -479,7 +487,7 @@ describe('Policy Engine Integration Tests', () => {
|
||||
expect(blockedToolRule?.priority).toBe(4.4); // Command line exclude
|
||||
|
||||
const blockedServerRule = rules.find(
|
||||
(r) => r.toolName === 'blocked-server__*',
|
||||
(r) => r.toolName === 'mcp_blocked-server_*',
|
||||
);
|
||||
expect(blockedServerRule?.priority).toBe(4.9); // MCP server exclude
|
||||
|
||||
@@ -489,11 +497,13 @@ describe('Policy Engine Integration Tests', () => {
|
||||
expect(specificToolRule?.priority).toBe(4.3); // Command line allow
|
||||
|
||||
const trustedServerRule = rules.find(
|
||||
(r) => r.toolName === 'trusted-server__*',
|
||||
(r) => r.toolName === 'mcp_trusted-server_*',
|
||||
);
|
||||
expect(trustedServerRule?.priority).toBe(4.2); // MCP trusted server
|
||||
|
||||
const mcpServerRule = rules.find((r) => r.toolName === 'mcp-server__*');
|
||||
const mcpServerRule = rules.find(
|
||||
(r) => r.toolName === 'mcp_mcp-server_*',
|
||||
);
|
||||
expect(mcpServerRule?.priority).toBe(4.1); // MCP allowed server
|
||||
|
||||
const readOnlyToolRule = rules.find((r) => r.toolName === 'glob');
|
||||
@@ -505,18 +515,19 @@ describe('Policy Engine Integration Tests', () => {
|
||||
(await engine.check({ name: 'blocked-tool' }, undefined)).decision,
|
||||
).toBe(PolicyDecision.DENY);
|
||||
expect(
|
||||
(await engine.check({ name: 'blocked-server__any' }, undefined))
|
||||
(await engine.check({ name: 'mcp_blocked-server_any' }, undefined))
|
||||
.decision,
|
||||
).toBe(PolicyDecision.DENY);
|
||||
expect(
|
||||
(await engine.check({ name: 'specific-tool' }, undefined)).decision,
|
||||
).toBe(PolicyDecision.ALLOW);
|
||||
expect(
|
||||
(await engine.check({ name: 'trusted-server__any' }, undefined))
|
||||
(await engine.check({ name: 'mcp_trusted-server_any' }, undefined))
|
||||
.decision,
|
||||
).toBe(PolicyDecision.ALLOW);
|
||||
expect(
|
||||
(await engine.check({ name: 'mcp-server__any' }, undefined)).decision,
|
||||
(await engine.check({ name: 'mcp_mcp-server_any' }, undefined))
|
||||
.decision,
|
||||
).toBe(PolicyDecision.ALLOW);
|
||||
expect((await engine.check({ name: 'glob' }, undefined)).decision).toBe(
|
||||
PolicyDecision.ALLOW,
|
||||
@@ -545,7 +556,7 @@ describe('Policy Engine Integration Tests', () => {
|
||||
|
||||
// Exclusion (195) should win over trust (90)
|
||||
expect(
|
||||
(await engine.check({ name: 'conflicted-server__tool' }, undefined))
|
||||
(await engine.check({ name: 'mcp_conflicted-server_tool' }, undefined))
|
||||
.decision,
|
||||
).toBe(PolicyDecision.DENY);
|
||||
});
|
||||
@@ -556,7 +567,7 @@ describe('Policy Engine Integration Tests', () => {
|
||||
excluded: ['my-server'], // Priority 195 - DENY
|
||||
},
|
||||
tools: {
|
||||
allowed: ['my-server__special-tool'], // Priority 100 - ALLOW
|
||||
allowed: ['mcp_my-server_special-tool'], // Priority 100 - ALLOW
|
||||
},
|
||||
};
|
||||
|
||||
@@ -569,11 +580,11 @@ describe('Policy Engine Integration Tests', () => {
|
||||
// Server exclusion (195) wins over specific tool allow (100)
|
||||
// This might be counterintuitive but follows the priority system
|
||||
expect(
|
||||
(await engine.check({ name: 'my-server__special-tool' }, undefined))
|
||||
(await engine.check({ name: 'mcp_my-server_special-tool' }, undefined))
|
||||
.decision,
|
||||
).toBe(PolicyDecision.DENY);
|
||||
expect(
|
||||
(await engine.check({ name: 'my-server__other-tool' }, undefined))
|
||||
(await engine.check({ name: 'mcp_my-server_other-tool' }, undefined))
|
||||
.decision,
|
||||
).toBe(PolicyDecision.DENY);
|
||||
});
|
||||
@@ -643,13 +654,13 @@ describe('Policy Engine Integration Tests', () => {
|
||||
const tool3Rule = rules.find((r) => r.toolName === 'tool3');
|
||||
expect(tool3Rule?.priority).toBe(4.4); // Excluded tools (user tier)
|
||||
|
||||
const server2Rule = rules.find((r) => r.toolName === 'server2__*');
|
||||
const server2Rule = rules.find((r) => r.toolName === 'mcp_server2_*');
|
||||
expect(server2Rule?.priority).toBe(4.9); // Excluded servers (user tier)
|
||||
|
||||
const tool1Rule = rules.find((r) => r.toolName === 'tool1');
|
||||
expect(tool1Rule?.priority).toBe(4.3); // Allowed tools (user tier)
|
||||
|
||||
const server1Rule = rules.find((r) => r.toolName === 'server1__*');
|
||||
const server1Rule = rules.find((r) => r.toolName === 'mcp_server1_*');
|
||||
expect(server1Rule?.priority).toBe(4.1); // Allowed servers (user tier)
|
||||
|
||||
const globRule = rules.find((r) => r.toolName === 'glob');
|
||||
|
||||
@@ -45,7 +45,7 @@ function getSandboxCommand(
|
||||
const environmentConfiguredSandbox =
|
||||
process.env['GEMINI_SANDBOX']?.toLowerCase().trim() ?? '';
|
||||
sandbox =
|
||||
environmentConfiguredSandbox.length > 0
|
||||
environmentConfiguredSandbox?.length > 0
|
||||
? environmentConfiguredSandbox
|
||||
: sandbox;
|
||||
if (sandbox === '1' || sandbox === 'true') sandbox = true;
|
||||
|
||||
@@ -460,7 +460,7 @@ describe('Settings Loading and Merging', () => {
|
||||
);
|
||||
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
expect(settings.merged.security.folderTrust?.enabled).toBe(false); // Workspace setting should be used
|
||||
expect(settings.merged.security?.folderTrust?.enabled).toBe(false); // Workspace setting should be used
|
||||
});
|
||||
|
||||
it('should use system folderTrust over user setting', () => {
|
||||
@@ -500,7 +500,7 @@ describe('Settings Loading and Merging', () => {
|
||||
);
|
||||
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
expect(settings.merged.security.folderTrust?.enabled).toBe(true); // System setting should be used
|
||||
expect(settings.merged.security?.folderTrust?.enabled).toBe(true); // System setting should be used
|
||||
});
|
||||
|
||||
it('should not allow user or workspace to override system disableYoloMode', () => {
|
||||
@@ -534,7 +534,7 @@ describe('Settings Loading and Merging', () => {
|
||||
);
|
||||
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
expect(settings.merged.security.disableYoloMode).toBe(true); // System setting should be used
|
||||
expect(settings.merged.security?.disableYoloMode).toBe(true); // System setting should be used
|
||||
});
|
||||
|
||||
it.each([
|
||||
@@ -630,7 +630,7 @@ describe('Settings Loading and Merging', () => {
|
||||
'WORKSPACE_DEBUG',
|
||||
'WORKSPACE_VAR',
|
||||
]);
|
||||
expect(settings.merged.advanced.excludedEnvVars).toEqual([
|
||||
expect(settings.merged.advanced?.excludedEnvVars).toEqual([
|
||||
'DEBUG',
|
||||
'DEBUG_MODE',
|
||||
'NODE_ENV',
|
||||
@@ -658,7 +658,7 @@ describe('Settings Loading and Merging', () => {
|
||||
);
|
||||
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
expect(settings.merged.context.fileName).toBeUndefined();
|
||||
expect(settings.merged.context?.fileName).toBeUndefined();
|
||||
});
|
||||
|
||||
it.each([
|
||||
@@ -991,7 +991,7 @@ describe('Settings Loading and Merging', () => {
|
||||
);
|
||||
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
expect(settings.merged.model.compressionThreshold).toEqual(expected);
|
||||
expect(settings.merged.model?.compressionThreshold).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1018,7 +1018,7 @@ describe('Settings Loading and Merging', () => {
|
||||
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
|
||||
expect(settings.merged.model.compressionThreshold).toEqual(0.5);
|
||||
expect(settings.merged.model?.compressionThreshold).toEqual(0.5);
|
||||
});
|
||||
|
||||
it('should merge includeDirectories from all scopes', () => {
|
||||
@@ -1052,7 +1052,7 @@ describe('Settings Loading and Merging', () => {
|
||||
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
|
||||
expect(settings.merged.context.includeDirectories).toEqual([
|
||||
expect(settings.merged.context?.includeDirectories).toEqual([
|
||||
'/system/defaults/dir',
|
||||
'/user/dir1',
|
||||
'/user/dir2',
|
||||
@@ -1247,7 +1247,7 @@ describe('Settings Loading and Merging', () => {
|
||||
expect((settings.merged as TestSettings)['workspaceOnly']).toBe(
|
||||
'workspace_value',
|
||||
);
|
||||
expect(settings.merged.ui.theme).toBe('light'); // workspace overrides user
|
||||
expect(settings.merged.ui?.theme).toBe('light'); // workspace overrides user
|
||||
|
||||
delete process.env['SYSTEM_VAR'];
|
||||
delete process.env['USER_VAR'];
|
||||
@@ -1275,7 +1275,7 @@ describe('Settings Loading and Merging', () => {
|
||||
);
|
||||
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
expect(settings.merged.advanced.dnsResolutionOrder).toBe('verbatim');
|
||||
expect(settings.merged.advanced?.dnsResolutionOrder).toBe('verbatim');
|
||||
});
|
||||
|
||||
it('should use user dnsResolutionOrder if workspace is not defined', () => {
|
||||
@@ -1294,7 +1294,7 @@ describe('Settings Loading and Merging', () => {
|
||||
);
|
||||
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
expect(settings.merged.advanced.dnsResolutionOrder).toBe('verbatim');
|
||||
expect(settings.merged.advanced?.dnsResolutionOrder).toBe('verbatim');
|
||||
});
|
||||
|
||||
it('should leave unresolved environment variables as is', () => {
|
||||
@@ -1599,7 +1599,7 @@ describe('Settings Loading and Merging', () => {
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
|
||||
// Verify the settings were loaded correctly
|
||||
expect(settings.merged.advanced.excludedEnvVars).toEqual([
|
||||
expect(settings.merged.advanced?.excludedEnvVars).toEqual([
|
||||
'DEBUG',
|
||||
'DEBUG_MODE',
|
||||
]);
|
||||
@@ -1637,7 +1637,7 @@ describe('Settings Loading and Merging', () => {
|
||||
'NODE_ENV',
|
||||
'DEBUG',
|
||||
]);
|
||||
expect(settings.merged.advanced.excludedEnvVars).toEqual([
|
||||
expect(settings.merged.advanced?.excludedEnvVars).toEqual([
|
||||
'DEBUG',
|
||||
'DEBUG_MODE',
|
||||
'NODE_ENV',
|
||||
@@ -1677,7 +1677,7 @@ describe('Settings Loading and Merging', () => {
|
||||
'WORKSPACE_DEBUG',
|
||||
'WORKSPACE_VAR',
|
||||
]);
|
||||
expect(settings.merged.advanced.excludedEnvVars).toEqual([
|
||||
expect(settings.merged.advanced?.excludedEnvVars).toEqual([
|
||||
'DEBUG',
|
||||
'DEBUG_MODE',
|
||||
'NODE_ENV',
|
||||
@@ -1711,9 +1711,9 @@ describe('Settings Loading and Merging', () => {
|
||||
);
|
||||
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
expect(settings.merged.tools.sandbox).toBe(true);
|
||||
expect(settings.merged.context.fileName).toBe('WORKSPACE.md');
|
||||
expect(settings.merged.ui.theme).toBe('dark');
|
||||
expect(settings.merged.tools?.sandbox).toBe(true);
|
||||
expect(settings.merged.context?.fileName).toBe('WORKSPACE.md');
|
||||
expect(settings.merged.ui?.theme).toBe('dark');
|
||||
});
|
||||
|
||||
it('should NOT merge workspace settings when workspace is not trusted', () => {
|
||||
@@ -1744,9 +1744,9 @@ describe('Settings Loading and Merging', () => {
|
||||
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
|
||||
expect(settings.merged.tools.sandbox).toBe(false); // User setting
|
||||
expect(settings.merged.context.fileName).toBe('USER.md'); // User setting
|
||||
expect(settings.merged.ui.theme).toBe('dark'); // User setting
|
||||
expect(settings.merged.tools?.sandbox).toBe(false); // User setting
|
||||
expect(settings.merged.context?.fileName).toBe('USER.md'); // User setting
|
||||
expect(settings.merged.ui?.theme).toBe('dark'); // User setting
|
||||
});
|
||||
|
||||
it('should NOT merge workspace settings when workspace trust is undefined', () => {
|
||||
@@ -1777,8 +1777,8 @@ describe('Settings Loading and Merging', () => {
|
||||
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
|
||||
expect(settings.merged.tools.sandbox).toBe(false); // User setting
|
||||
expect(settings.merged.context.fileName).toBe('USER.md'); // User setting
|
||||
expect(settings.merged.tools?.sandbox).toBe(false); // User setting
|
||||
expect(settings.merged.context?.fileName).toBe('USER.md'); // User setting
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2229,7 +2229,7 @@ describe('Settings Loading and Merging', () => {
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
|
||||
// Verify it was migrated in the merged settings
|
||||
expect(settings.merged.general.enableAutoUpdate).toBe(false);
|
||||
expect(settings.merged.general?.enableAutoUpdate).toBe(false);
|
||||
|
||||
// Verify it was saved back to disk (via setValue calling updateSettingsFilePreservingFormat)
|
||||
expect(updateSettingsFilePreservingFormat).toHaveBeenCalledWith(
|
||||
@@ -2289,7 +2289,7 @@ describe('Settings Loading and Merging', () => {
|
||||
).toBe(true);
|
||||
|
||||
// Merged should also reflect it (system overrides defaults, but both are migrated)
|
||||
expect(settings.merged.general.enableAutoUpdateNotification).toBe(false);
|
||||
expect(settings.merged.general?.enableAutoUpdateNotification).toBe(false);
|
||||
|
||||
// Verify it was NOT saved back to disk
|
||||
expect(updateSettingsFilePreservingFormat).not.toHaveBeenCalledWith(
|
||||
@@ -2487,10 +2487,10 @@ describe('Settings Loading and Merging', () => {
|
||||
|
||||
// 1. Verify that on initial load, file-based admin settings are ignored
|
||||
// and schema defaults are used instead.
|
||||
expect(loadedSettings.merged.admin.secureModeEnabled).toBe(false); // default: false
|
||||
expect(loadedSettings.merged.admin.mcp?.enabled).toBe(true); // default: true
|
||||
expect(loadedSettings.merged.admin.extensions?.enabled).toBe(true); // default: true
|
||||
expect(loadedSettings.merged.ui.theme).toBe('system-theme'); // non-admin setting should be loaded
|
||||
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false); // default: false
|
||||
expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(true); // default: true
|
||||
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(true); // default: true
|
||||
expect(loadedSettings.merged.ui?.theme).toBe('system-theme'); // non-admin setting should be loaded
|
||||
|
||||
// 2. Now, set remote admin settings.
|
||||
loadedSettings.setRemoteAdminSettings({
|
||||
@@ -2503,11 +2503,11 @@ describe('Settings Loading and Merging', () => {
|
||||
});
|
||||
|
||||
// 3. Verify that remote admin settings take precedence.
|
||||
expect(loadedSettings.merged.admin.secureModeEnabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin.mcp?.enabled).toBe(false);
|
||||
expect(loadedSettings.merged.admin.extensions?.enabled).toBe(false);
|
||||
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(false);
|
||||
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(false);
|
||||
// non-admin setting should remain unchanged
|
||||
expect(loadedSettings.merged.ui.theme).toBe('system-theme');
|
||||
expect(loadedSettings.merged.ui?.theme).toBe('system-theme');
|
||||
});
|
||||
|
||||
it('should set remote admin settings and recompute merged settings', () => {
|
||||
@@ -2532,10 +2532,10 @@ describe('Settings Loading and Merging', () => {
|
||||
|
||||
const loadedSettings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
// Ensure initial state from defaults (as file-based admin settings are ignored)
|
||||
expect(loadedSettings.merged.admin.secureModeEnabled).toBe(false);
|
||||
expect(loadedSettings.merged.admin.mcp?.enabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin.extensions?.enabled).toBe(true);
|
||||
expect(loadedSettings.merged.ui.theme).toBe('initial-theme');
|
||||
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false);
|
||||
expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(true);
|
||||
expect(loadedSettings.merged.ui?.theme).toBe('initial-theme');
|
||||
|
||||
const newRemoteSettings = {
|
||||
strictModeDisabled: false,
|
||||
@@ -2549,11 +2549,11 @@ describe('Settings Loading and Merging', () => {
|
||||
loadedSettings.setRemoteAdminSettings(newRemoteSettings);
|
||||
|
||||
// Verify that remote admin settings are applied
|
||||
expect(loadedSettings.merged.admin.secureModeEnabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin.mcp?.enabled).toBe(false);
|
||||
expect(loadedSettings.merged.admin.extensions?.enabled).toBe(false);
|
||||
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(false);
|
||||
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(false);
|
||||
// Non-admin settings should remain untouched
|
||||
expect(loadedSettings.merged.ui.theme).toBe('initial-theme');
|
||||
expect(loadedSettings.merged.ui?.theme).toBe('initial-theme');
|
||||
});
|
||||
|
||||
it('should correctly handle undefined remote admin settings', () => {
|
||||
@@ -2573,16 +2573,16 @@ describe('Settings Loading and Merging', () => {
|
||||
|
||||
const loadedSettings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
// Should have default admin settings
|
||||
expect(loadedSettings.merged.admin.secureModeEnabled).toBe(false);
|
||||
expect(loadedSettings.merged.admin.mcp?.enabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin.extensions?.enabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false);
|
||||
expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(true);
|
||||
|
||||
loadedSettings.setRemoteAdminSettings({}); // Set empty remote settings
|
||||
|
||||
// Admin settings should revert to defaults because there are no remote overrides
|
||||
expect(loadedSettings.merged.admin.secureModeEnabled).toBe(false);
|
||||
expect(loadedSettings.merged.admin.mcp?.enabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin.extensions?.enabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false);
|
||||
expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(true);
|
||||
});
|
||||
|
||||
it('should un-nest MCP configuration from remote settings', () => {
|
||||
@@ -2604,7 +2604,7 @@ describe('Settings Loading and Merging', () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(loadedSettings.merged.admin.mcp?.config).toEqual(mcpServers);
|
||||
expect(loadedSettings.merged.admin?.mcp?.config).toEqual(mcpServers);
|
||||
});
|
||||
|
||||
it('should set skills based on unmanagedCapabilitiesEnabled', () => {
|
||||
@@ -2630,9 +2630,9 @@ describe('Settings Loading and Merging', () => {
|
||||
loadedSettings.setRemoteAdminSettings({});
|
||||
|
||||
// Should default to schema defaults (standard defaults)
|
||||
expect(loadedSettings.merged.admin.secureModeEnabled).toBe(false);
|
||||
expect(loadedSettings.merged.admin.mcp?.enabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin.extensions?.enabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false);
|
||||
expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -54,20 +54,24 @@ import {
|
||||
export function getMergeStrategyForPath(
|
||||
path: string[],
|
||||
): MergeStrategy | undefined {
|
||||
let current: SettingDefinition | undefined = undefined;
|
||||
let currentSchema: SettingsSchema | undefined = getSettingsSchema();
|
||||
let parent: SettingDefinition | undefined = undefined;
|
||||
|
||||
for (const key of path) {
|
||||
const current: SettingDefinition | undefined = currentSchema?.[key];
|
||||
if (!current) {
|
||||
if (!currentSchema || !currentSchema[key]) {
|
||||
// Key not found in schema - check if parent has additionalProperties
|
||||
return parent?.additionalProperties?.mergeStrategy;
|
||||
if (parent?.additionalProperties?.mergeStrategy) {
|
||||
return parent.additionalProperties.mergeStrategy;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
parent = current;
|
||||
current = currentSchema[key];
|
||||
currentSchema = current.properties;
|
||||
}
|
||||
|
||||
return parent?.mergeStrategy;
|
||||
return current?.mergeStrategy;
|
||||
}
|
||||
|
||||
export const USER_SETTINGS_PATH = Storage.getGlobalSettingsPath();
|
||||
@@ -368,17 +372,19 @@ export class LoadedSettings {
|
||||
// Remote admin settings always take precedence and file-based admin settings
|
||||
// are ignored.
|
||||
const adminSettingSchema = getSettingsSchema().admin;
|
||||
const adminSchema = adminSettingSchema.properties;
|
||||
const adminDefaults = getDefaultsFromSchema(adminSchema);
|
||||
if (adminSettingSchema?.properties) {
|
||||
const adminSchema = adminSettingSchema.properties;
|
||||
const adminDefaults = getDefaultsFromSchema(adminSchema);
|
||||
|
||||
// The final admin settings are the defaults overridden by remote settings.
|
||||
// Any admin settings from files are ignored.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
merged.admin = customDeepMerge(
|
||||
(path: string[]) => getMergeStrategyForPath(['admin', ...path]),
|
||||
adminDefaults,
|
||||
this._remoteAdminSettings?.admin ?? {},
|
||||
) as MergedSettings['admin'];
|
||||
// The final admin settings are the defaults overridden by remote settings.
|
||||
// Any admin settings from files are ignored.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
merged.admin = customDeepMerge(
|
||||
(path: string[]) => getMergeStrategyForPath(['admin', ...path]),
|
||||
adminDefaults,
|
||||
this._remoteAdminSettings?.admin ?? {},
|
||||
) as MergedSettings['admin'];
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
@@ -487,7 +493,7 @@ export class LoadedSettings {
|
||||
|
||||
function findEnvFile(startDir: string): string | null {
|
||||
let currentDir = path.resolve(startDir);
|
||||
for (;;) {
|
||||
while (true) {
|
||||
// prefer gemini-specific .env under GEMINI_DIR
|
||||
const geminiEnvPath = path.join(currentDir, GEMINI_DIR, '.env');
|
||||
if (fs.existsSync(geminiEnvPath)) {
|
||||
@@ -577,7 +583,7 @@ export function loadEnvironment(
|
||||
const parsedEnv = dotenv.parse(envFileContent);
|
||||
|
||||
const excludedVars =
|
||||
settings.advanced?.excludedEnvVars || DEFAULT_EXCLUDED_ENV_VARS;
|
||||
settings?.advanced?.excludedEnvVars || DEFAULT_EXCLUDED_ENV_VARS;
|
||||
const isProjectEnvFile = !envFilePath.includes(GEMINI_DIR);
|
||||
|
||||
for (const key in parsedEnv) {
|
||||
@@ -1079,7 +1085,7 @@ function migrateExperimentalSettings(
|
||||
};
|
||||
const agentsOverrides = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
...(agentsSettings['overrides'] as Record<string, unknown>),
|
||||
...((agentsSettings['overrides'] as Record<string, unknown>) || {}),
|
||||
};
|
||||
let modified = false;
|
||||
|
||||
|
||||
@@ -1806,15 +1806,6 @@ const SETTINGS_SCHEMA = {
|
||||
description: 'Enable planning features (Plan Mode and tools).',
|
||||
showInDialog: true,
|
||||
},
|
||||
taskTracker: {
|
||||
type: 'boolean',
|
||||
label: 'Task Tracker',
|
||||
category: 'Experimental',
|
||||
requiresRestart: true,
|
||||
default: false,
|
||||
description: 'Enable task tracker tools.',
|
||||
showInDialog: false,
|
||||
},
|
||||
modelSteering: {
|
||||
type: 'boolean',
|
||||
label: 'Model Steering',
|
||||
|
||||
@@ -195,7 +195,7 @@ describe('Settings Repro', () => {
|
||||
// If it doesn't throw, check if it merged correctly.
|
||||
// The model.compressionThreshold should be present.
|
||||
// And model.name should probably be undefined or default, but certainly NOT { compressionThreshold: 0.8 }
|
||||
expect(settings.merged.model.compressionThreshold).toBe(0.8);
|
||||
expect(typeof settings.merged.model.name).not.toBe('object');
|
||||
expect(settings.merged.model?.compressionThreshold).toBe(0.8);
|
||||
expect(typeof settings.merged.model?.name).not.toBe('object');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,7 +33,7 @@ export async function runDeferredCommand(settings: MergedSettings) {
|
||||
const adminSettings = settings.admin;
|
||||
const commandName = deferredCommand.commandName;
|
||||
|
||||
if (commandName === 'mcp' && adminSettings.mcp.enabled === false) {
|
||||
if (commandName === 'mcp' && adminSettings?.mcp?.enabled === false) {
|
||||
coreEvents.emitFeedback(
|
||||
'error',
|
||||
getAdminErrorMessage('MCP', undefined /* config */),
|
||||
@@ -44,7 +44,7 @@ export async function runDeferredCommand(settings: MergedSettings) {
|
||||
|
||||
if (
|
||||
commandName === 'extensions' &&
|
||||
adminSettings.extensions.enabled === false
|
||||
adminSettings?.extensions?.enabled === false
|
||||
) {
|
||||
coreEvents.emitFeedback(
|
||||
'error',
|
||||
@@ -54,7 +54,7 @@ export async function runDeferredCommand(settings: MergedSettings) {
|
||||
process.exit(ExitCodes.FATAL_CONFIG_ERROR);
|
||||
}
|
||||
|
||||
if (commandName === 'skills' && adminSettings.skills.enabled === false) {
|
||||
if (commandName === 'skills' && adminSettings?.skills?.enabled === false) {
|
||||
coreEvents.emitFeedback(
|
||||
'error',
|
||||
getAdminErrorMessage('Agent skills', undefined /* config */),
|
||||
|
||||
@@ -79,7 +79,7 @@ import {
|
||||
type InitializationResult,
|
||||
} from './core/initializer.js';
|
||||
import { validateAuthMethod } from './config/auth.js';
|
||||
import { runZedIntegration } from './zed-integration/zedIntegration.js';
|
||||
import { runAcpClient } from './acp/acpClient.js';
|
||||
import { validateNonInteractiveAuth } from './validateNonInterActiveAuth.js';
|
||||
import { checkForUpdates } from './ui/utils/updateCheck.js';
|
||||
import { handleAutoUpdate } from './utils/handleAutoUpdate.js';
|
||||
@@ -378,7 +378,7 @@ export async function main() {
|
||||
|
||||
if (
|
||||
(argv.allowedTools && argv.allowedTools.length > 0) ||
|
||||
(settings.merged.tools.allowed && settings.merged.tools.allowed.length > 0)
|
||||
(settings.merged.tools?.allowed && settings.merged.tools.allowed.length > 0)
|
||||
) {
|
||||
coreEvents.emitFeedback(
|
||||
'warning',
|
||||
@@ -387,7 +387,7 @@ export async function main() {
|
||||
}
|
||||
|
||||
if (
|
||||
settings.merged.tools.exclude &&
|
||||
settings.merged.tools?.exclude &&
|
||||
settings.merged.tools.exclude.length > 0
|
||||
) {
|
||||
coreEvents.emitFeedback(
|
||||
@@ -672,8 +672,8 @@ export async function main() {
|
||||
await getOauthClient(settings.merged.security.auth.selectedType, config);
|
||||
}
|
||||
|
||||
if (config.getExperimentalZedIntegration()) {
|
||||
return runZedIntegration(config, settings, argv);
|
||||
if (config.getAcpMode()) {
|
||||
return runAcpClient(config, settings, argv);
|
||||
}
|
||||
|
||||
let input = config.getQuestion();
|
||||
@@ -748,7 +748,7 @@ export async function main() {
|
||||
? SessionStartSource.Resume
|
||||
: SessionStartSource.Startup;
|
||||
|
||||
const hookSystem = config.getHookSystem();
|
||||
const hookSystem = config?.getHookSystem();
|
||||
if (hookSystem) {
|
||||
const result = await hookSystem.fireSessionStartEvent(sessionStartSource);
|
||||
|
||||
@@ -784,7 +784,7 @@ export async function main() {
|
||||
new UserPromptEvent(
|
||||
input.length,
|
||||
prompt_id,
|
||||
config.getContentGeneratorConfig().authType,
|
||||
config.getContentGeneratorConfig()?.authType,
|
||||
input,
|
||||
),
|
||||
);
|
||||
@@ -881,7 +881,7 @@ function setupAdminControlsListener() {
|
||||
type?: string;
|
||||
settings?: AdminControlsSettings;
|
||||
};
|
||||
if (message.type === 'admin-settings' && message.settings) {
|
||||
if (message?.type === 'admin-settings' && message.settings) {
|
||||
if (config) {
|
||||
config.setRemoteAdminSettings(message.settings);
|
||||
} else {
|
||||
|
||||
@@ -179,7 +179,7 @@ describe('gemini.tsx main function cleanup', () => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should log error when cleanupExpiredSessions fails', async () => {
|
||||
it.skip('should log error when cleanupExpiredSessions fails', async () => {
|
||||
const { loadCliConfig, parseArguments } = await import(
|
||||
'./config/config.js'
|
||||
);
|
||||
@@ -216,7 +216,7 @@ describe('gemini.tsx main function cleanup', () => {
|
||||
getMcpServers: () => ({}),
|
||||
getMcpClientManager: vi.fn(),
|
||||
getIdeMode: vi.fn(() => false),
|
||||
getExperimentalZedIntegration: vi.fn(() => true),
|
||||
getAcpMode: vi.fn(() => true),
|
||||
getScreenReader: vi.fn(() => false),
|
||||
getGeminiMdFileCount: vi.fn(() => 0),
|
||||
getProjectRoot: vi.fn(() => '/'),
|
||||
|
||||
@@ -135,7 +135,7 @@ export async function runNonInteractive({
|
||||
key: { name?: string; ctrl?: boolean },
|
||||
) => {
|
||||
// Detect Ctrl+C: either ctrl+c key combo or raw character code 3
|
||||
if ((key.ctrl && key.name === 'c') || str === '\u0003') {
|
||||
if ((key && key.ctrl && key.name === 'c') || str === '\u0003') {
|
||||
// Only handle once
|
||||
if (isAborting) {
|
||||
return;
|
||||
@@ -289,7 +289,7 @@ export async function runNonInteractive({
|
||||
let currentMessages: Content[] = [{ role: 'user', parts: query }];
|
||||
|
||||
let turnCount = 0;
|
||||
for (;;) {
|
||||
while (true) {
|
||||
turnCount++;
|
||||
if (
|
||||
config.getMaxSessionTurns() >= 0 &&
|
||||
@@ -461,8 +461,8 @@ export async function runNonInteractive({
|
||||
(tc) => tc.response.errorType === ToolErrorType.STOP_EXECUTION,
|
||||
);
|
||||
|
||||
if (stopExecutionTool) {
|
||||
const stopMessage = `Agent execution stopped: ${stopExecutionTool.response.error?.message}`;
|
||||
if (stopExecutionTool && stopExecutionTool.response.error) {
|
||||
const stopMessage = `Agent execution stopped: ${stopExecutionTool.response.error.message}`;
|
||||
|
||||
if (config.getOutputFormat() === OutputFormat.TEXT) {
|
||||
process.stderr.write(`${stopMessage}\n`);
|
||||
|
||||
@@ -56,14 +56,14 @@ export const handleSlashCommand = async (
|
||||
if (commandToExecute.action) {
|
||||
// Not used by custom commands but may be in the future.
|
||||
const sessionStats: SessionStatsState = {
|
||||
sessionId: config.getSessionId(),
|
||||
sessionId: config?.getSessionId(),
|
||||
sessionStartTime: new Date(),
|
||||
metrics: uiTelemetryService.getMetrics(),
|
||||
lastPromptTokenCount: 0,
|
||||
promptCount: 1,
|
||||
};
|
||||
|
||||
const logger = new Logger(config.getSessionId() || '', config.storage);
|
||||
const logger = new Logger(config?.getSessionId() || '', config?.storage);
|
||||
|
||||
const context: CommandContext = {
|
||||
services: {
|
||||
|
||||
@@ -158,7 +158,7 @@ export class BuiltinCommandLoader implements ICommandLoader {
|
||||
themeCommand,
|
||||
toolsCommand,
|
||||
...(this.config?.isSkillsSupportEnabled()
|
||||
? this.config.getSkillManager().isAdminEnabled() === false
|
||||
? this.config?.getSkillManager()?.isAdminEnabled() === false
|
||||
? [
|
||||
{
|
||||
name: 'skills',
|
||||
|
||||
@@ -126,7 +126,7 @@ export class FileCommandLoader implements ICommandLoader {
|
||||
if (
|
||||
!signal.aborted &&
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
(error as { code?: string }).code !== 'ENOENT'
|
||||
(error as { code?: string })?.code !== 'ENOENT'
|
||||
) {
|
||||
coreEvents.emitFeedback(
|
||||
'error',
|
||||
|
||||
@@ -62,7 +62,7 @@ export class McpPromptLoader implements ICommandLoader {
|
||||
let helpMessage = `Arguments for "${prompt.name}":\n\n`;
|
||||
if (prompt.arguments && prompt.arguments.length > 0) {
|
||||
helpMessage += `You can provide arguments by name (e.g., --argName="value") or by position.\n\n`;
|
||||
helpMessage += `e.g., ${prompt.name} ${prompt.arguments.map((_) => `"foo"`)} is equivalent to ${prompt.name} ${prompt.arguments.map((arg) => `--${arg.name}="foo"`)}\n\n`;
|
||||
helpMessage += `e.g., ${prompt.name} ${prompt.arguments?.map((_) => `"foo"`)} is equivalent to ${prompt.name} ${prompt.arguments?.map((arg) => `--${arg.name}="foo"`)}\n\n`;
|
||||
}
|
||||
for (const arg of prompt.arguments) {
|
||||
helpMessage += ` --${arg.name}\n`;
|
||||
@@ -123,7 +123,7 @@ export class McpPromptLoader implements ICommandLoader {
|
||||
};
|
||||
}
|
||||
|
||||
const maybeContent = result.messages[0]?.content;
|
||||
const maybeContent = result.messages?.[0]?.content;
|
||||
if (maybeContent.type !== 'text') {
|
||||
return {
|
||||
type: 'message',
|
||||
|
||||
@@ -119,7 +119,7 @@ export class ShellProcessor implements IPromptProcessor {
|
||||
|
||||
if (!command) continue;
|
||||
|
||||
if (context.session.sessionShellAllowlist.has(command)) {
|
||||
if (context.session.sessionShellAllowlist?.has(command)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -312,7 +312,7 @@ export class AppRig {
|
||||
const details = call.confirmationDetails;
|
||||
const title = 'title' in details ? details.title : '';
|
||||
const toolDisplayName =
|
||||
call.tool.displayName || title.replace(/^Confirm:\s*/, '');
|
||||
call.tool?.displayName || title.replace(/^Confirm:\s*/, '');
|
||||
if (!this.pendingConfirmations.has(call.correlationId)) {
|
||||
this.pendingConfirmations.set(call.correlationId, {
|
||||
toolName: call.request.name,
|
||||
@@ -469,7 +469,7 @@ export class AppRig {
|
||||
} = options;
|
||||
const start = Date.now();
|
||||
|
||||
for (;;) {
|
||||
while (true) {
|
||||
if (await predicate()) return;
|
||||
|
||||
if (Date.now() - start > timeout) {
|
||||
@@ -622,7 +622,7 @@ export class AppRig {
|
||||
onConfirmation?: (confirmation: PendingConfirmation) => void | boolean,
|
||||
timeout = 60000,
|
||||
) {
|
||||
for (;;) {
|
||||
while (true) {
|
||||
const event = await this.waitForNextEvent(timeout);
|
||||
if (event.type === 'idle') {
|
||||
break;
|
||||
@@ -710,7 +710,7 @@ export class AppRig {
|
||||
if (this.config) {
|
||||
const recordingService = this.config
|
||||
.getGeminiClient()
|
||||
.getChatRecordingService();
|
||||
?.getChatRecordingService();
|
||||
if (recordingService) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion
|
||||
(recordingService as any).conversationFile = null;
|
||||
|
||||
@@ -18,7 +18,7 @@ export async function waitFor(
|
||||
): Promise<void> {
|
||||
const startTime = Date.now();
|
||||
|
||||
for (;;) {
|
||||
while (true) {
|
||||
try {
|
||||
await assertion();
|
||||
return;
|
||||
|
||||
@@ -42,7 +42,7 @@ export const createMockConfig = (overrides: Partial<Config> = {}): Config =>
|
||||
setSessionId: vi.fn(),
|
||||
getSessionId: vi.fn().mockReturnValue('mock-session-id'),
|
||||
getContentGeneratorConfig: vi.fn(() => ({ authType: 'google' })),
|
||||
getExperimentalZedIntegration: vi.fn(() => false),
|
||||
getAcpMode: vi.fn(() => false),
|
||||
isBrowserLaunchSuppressed: vi.fn(() => false),
|
||||
setRemoteAdminSettings: vi.fn(),
|
||||
isYoloModeDisabled: vi.fn(() => false),
|
||||
|
||||
@@ -47,7 +47,7 @@ export const generateSvgForTerminal = (terminal: Terminal): string => {
|
||||
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('')}`;
|
||||
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');
|
||||
@@ -89,7 +89,6 @@ export const generateSvgForTerminal = (terminal: Terminal): string => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (contentRows === 0) contentRows = 1; // Minimum 1 row
|
||||
|
||||
const width = terminal.cols * charWidth + padding * 2;
|
||||
@@ -114,9 +113,6 @@ export const generateSvgForTerminal = (terminal: Terminal): string => {
|
||||
|
||||
let currentFgHex: string | null = null;
|
||||
let currentBgHex: string | null = null;
|
||||
let currentIsBold = false;
|
||||
let currentIsItalic = false;
|
||||
let currentIsUnderline = false;
|
||||
let currentBlockStartCol = -1;
|
||||
let currentBlockText = '';
|
||||
let currentBlockNumCells = 0;
|
||||
@@ -132,20 +128,12 @@ export const generateSvgForTerminal = (terminal: Terminal): string => {
|
||||
svg += ` <rect x="${xPos}" y="${yPos}" width="${rectWidth}" height="${charHeight}" fill="${currentBgHex}" />
|
||||
`;
|
||||
}
|
||||
if (currentBlockText.trim().length > 0 || currentIsUnderline) {
|
||||
if (currentBlockText.trim().length > 0) {
|
||||
const fill = currentFgHex || '#ffffff'; // Default text color
|
||||
const textWidth = currentBlockNumCells * charWidth;
|
||||
|
||||
let extraAttrs = '';
|
||||
if (currentIsBold) extraAttrs += ' font-weight="bold"';
|
||||
if (currentIsItalic) extraAttrs += ' font-style="italic"';
|
||||
if (currentIsUnderline)
|
||||
extraAttrs += ' text-decoration="underline"';
|
||||
|
||||
// Use textLength to ensure the block fits exactly into its designated cells
|
||||
const textElement = `<text x="${xPos}" y="${yPos + 2}" fill="${fill}" textLength="${textWidth}" lengthAdjust="spacingAndGlyphs"${extraAttrs}>${escapeXml(currentBlockText)}</text>`;
|
||||
|
||||
svg += ` ${textElement}\n`;
|
||||
svg += ` <text x="${xPos}" y="${yPos + 2}" fill="${fill}" textLength="${textWidth}" lengthAdjust="spacingAndGlyphs">${escapeXml(currentBlockText)}</text>
|
||||
`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -176,27 +164,17 @@ export const generateSvgForTerminal = (terminal: Terminal): string => {
|
||||
bgHex = tempFgHex || '#ffffff';
|
||||
}
|
||||
|
||||
const isBold = !!cell.isBold();
|
||||
const isItalic = !!cell.isItalic();
|
||||
const isUnderline = !!cell.isUnderline();
|
||||
|
||||
let chars = cell.getChars();
|
||||
if (chars === '') chars = ' '.repeat(cellWidth);
|
||||
|
||||
if (
|
||||
fgHex !== currentFgHex ||
|
||||
bgHex !== currentBgHex ||
|
||||
isBold !== currentIsBold ||
|
||||
isItalic !== currentIsItalic ||
|
||||
isUnderline !== currentIsUnderline ||
|
||||
currentBlockStartCol === -1
|
||||
) {
|
||||
finalizeBlock(x);
|
||||
currentFgHex = fgHex;
|
||||
currentBgHex = bgHex;
|
||||
currentIsBold = isBold;
|
||||
currentIsItalic = isItalic;
|
||||
currentIsUnderline = isUnderline;
|
||||
currentBlockStartCol = x;
|
||||
currentBlockText = chars;
|
||||
currentBlockNumCells = cellWidth;
|
||||
@@ -207,7 +185,6 @@ export const generateSvgForTerminal = (terminal: Terminal): string => {
|
||||
}
|
||||
finalizeBlock(line.length);
|
||||
}
|
||||
|
||||
svg += ` </g>\n</svg>`;
|
||||
return svg;
|
||||
};
|
||||
|
||||
@@ -129,7 +129,7 @@ import { appEvents, AppEvent, TransientMessageType } from '../utils/events.js';
|
||||
import { type UpdateObject } from './utils/updateCheck.js';
|
||||
import { setUpdateHandler } from '../utils/handleAutoUpdate.js';
|
||||
import { registerCleanup, runExitCleanup } from '../utils/cleanup.js';
|
||||
import { relaunchApp } from '../utils/processUtils.js';
|
||||
import { RELAUNCH_EXIT_CODE } from '../utils/processUtils.js';
|
||||
import type { SessionInfo } from '../utils/sessionUtils.js';
|
||||
import { useMessageQueue } from './hooks/useMessageQueue.js';
|
||||
import { useMcpStatus } from './hooks/useMcpStatus.js';
|
||||
@@ -781,12 +781,13 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
authType === AuthType.LOGIN_WITH_GOOGLE &&
|
||||
config.isBrowserLaunchSuppressed()
|
||||
) {
|
||||
await runExitCleanup();
|
||||
writeToStdout(`
|
||||
----------------------------------------------------------------
|
||||
Logging in with Google... Restarting Gemini CLI to continue.
|
||||
----------------------------------------------------------------
|
||||
`);
|
||||
await relaunchApp();
|
||||
process.exit(RELAUNCH_EXIT_CODE);
|
||||
}
|
||||
}
|
||||
setAuthState(AuthState.Authenticated);
|
||||
@@ -2496,7 +2497,8 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
});
|
||||
}
|
||||
}
|
||||
await relaunchApp();
|
||||
await runExitCleanup();
|
||||
process.exit(RELAUNCH_EXIT_CODE);
|
||||
},
|
||||
handleNewAgentsSelect: async (choice: NewAgentsChoice) => {
|
||||
if (newAgents && choice === NewAgentsChoice.ACKNOWLEDGE) {
|
||||
|
||||
@@ -73,8 +73,12 @@ export function IdeIntegrationNudge({
|
||||
];
|
||||
|
||||
const installText = isExtensionPreInstalled
|
||||
? `If you select Yes, the CLI will have access to your open files and display diffs directly in ${ideName}.`
|
||||
: `If you select Yes, we'll install an extension that allows the CLI to access your open files and display diffs directly in ${ideName}.`;
|
||||
? `If you select Yes, the CLI will have access to your open files and display diffs directly in ${
|
||||
ideName ?? 'your editor'
|
||||
}.`
|
||||
: `If you select Yes, we'll install an extension that allows the CLI to access your open files and display diffs directly in ${
|
||||
ideName ?? 'your editor'
|
||||
}.`;
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -88,7 +92,7 @@ export function IdeIntegrationNudge({
|
||||
<Box marginBottom={1} flexDirection="column">
|
||||
<Text>
|
||||
<Text color={theme.status.warning}>{'> '}</Text>
|
||||
{`Do you want to connect ${ideName} to Gemini CLI?`}
|
||||
{`Do you want to connect ${ideName ?? 'your editor'} to Gemini CLI?`}
|
||||
</Text>
|
||||
<Text color={theme.text.secondary}>{installText}</Text>
|
||||
</Box>
|
||||
|
||||
@@ -44,7 +44,7 @@ export function ApiAuthDialog({
|
||||
|
||||
const buffer = useTextBuffer({
|
||||
initialText: initialApiKey || '',
|
||||
initialCursorOffset: initialApiKey.length || 0,
|
||||
initialCursorOffset: initialApiKey?.length || 0,
|
||||
viewport: {
|
||||
width: viewportWidth,
|
||||
height: 4,
|
||||
@@ -98,7 +98,7 @@ export function ApiAuthDialog({
|
||||
return (
|
||||
<Box
|
||||
borderStyle="round"
|
||||
borderColor={theme.ui.focus}
|
||||
borderColor={theme.border.focused}
|
||||
flexDirection="column"
|
||||
padding={1}
|
||||
width="100%"
|
||||
|
||||
@@ -21,8 +21,9 @@ import {
|
||||
} from '@google/gemini-cli-core';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { AuthState } from '../types.js';
|
||||
import { runExitCleanup } from '../../utils/cleanup.js';
|
||||
import { validateAuthMethodWithSettings } from './useAuth.js';
|
||||
import { relaunchApp } from '../../utils/processUtils.js';
|
||||
import { RELAUNCH_EXIT_CODE } from '../../utils/processUtils.js';
|
||||
|
||||
interface AuthDialogProps {
|
||||
config: Config;
|
||||
@@ -132,7 +133,10 @@ export function AuthDialog({
|
||||
config.isBrowserLaunchSuppressed()
|
||||
) {
|
||||
setExiting(true);
|
||||
setTimeout(relaunchApp, 100);
|
||||
setTimeout(async () => {
|
||||
await runExitCleanup();
|
||||
process.exit(RELAUNCH_EXIT_CODE);
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -189,7 +193,7 @@ export function AuthDialog({
|
||||
return (
|
||||
<Box
|
||||
borderStyle="round"
|
||||
borderColor={theme.ui.focus}
|
||||
borderColor={theme.border.focused}
|
||||
flexDirection="row"
|
||||
padding={1}
|
||||
width="100%"
|
||||
@@ -205,7 +209,7 @@ export function AuthDialog({
|
||||
return (
|
||||
<Box
|
||||
borderStyle="round"
|
||||
borderColor={theme.ui.focus}
|
||||
borderColor={theme.border.focused}
|
||||
flexDirection="row"
|
||||
padding={1}
|
||||
width="100%"
|
||||
|
||||
@@ -9,10 +9,7 @@ import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
|
||||
import { LoginWithGoogleRestartDialog } from './LoginWithGoogleRestartDialog.js';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { runExitCleanup } from '../../utils/cleanup.js';
|
||||
import {
|
||||
RELAUNCH_EXIT_CODE,
|
||||
_resetRelaunchStateForTesting,
|
||||
} from '../../utils/processUtils.js';
|
||||
import { RELAUNCH_EXIT_CODE } from '../../utils/processUtils.js';
|
||||
import { type Config } from '@google/gemini-cli-core';
|
||||
|
||||
// Mocks
|
||||
@@ -41,7 +38,6 @@ describe('LoginWithGoogleRestartDialog', () => {
|
||||
vi.clearAllMocks();
|
||||
exitSpy.mockClear();
|
||||
vi.useRealTimers();
|
||||
_resetRelaunchStateForTesting();
|
||||
});
|
||||
|
||||
it('renders correctly', async () => {
|
||||
|
||||
@@ -8,7 +8,8 @@ import { type Config } from '@google/gemini-cli-core';
|
||||
import { Box, Text } from 'ink';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { relaunchApp } from '../../utils/processUtils.js';
|
||||
import { runExitCleanup } from '../../utils/cleanup.js';
|
||||
import { RELAUNCH_EXIT_CODE } from '../../utils/processUtils.js';
|
||||
|
||||
interface LoginWithGoogleRestartDialogProps {
|
||||
onDismiss: () => void;
|
||||
@@ -35,7 +36,8 @@ export const LoginWithGoogleRestartDialog = ({
|
||||
});
|
||||
}
|
||||
}
|
||||
await relaunchApp();
|
||||
await runExitCleanup();
|
||||
process.exit(RELAUNCH_EXIT_CODE);
|
||||
}, 100);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -68,5 +68,5 @@ async function getIdeClientName(context: CommandContext) {
|
||||
return '';
|
||||
}
|
||||
const ideClient = await IdeClient.getInstance();
|
||||
return ideClient.getDetectedIdeDisplayName() ?? '';
|
||||
return ideClient?.getDetectedIdeDisplayName() ?? '';
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ async function enableAction(
|
||||
const allAgents = agentRegistry.getAllAgentNames();
|
||||
const overrides = settings.merged.agents.overrides;
|
||||
const disabledAgents = Object.keys(overrides).filter(
|
||||
(name) => overrides[name].enabled === false,
|
||||
(name) => overrides[name]?.enabled === false,
|
||||
);
|
||||
|
||||
if (allAgents.includes(agentName) && !disabledAgents.includes(agentName)) {
|
||||
@@ -167,7 +167,7 @@ async function disableAction(
|
||||
const allAgents = agentRegistry.getAllAgentNames();
|
||||
const overrides = settings.merged.agents.overrides;
|
||||
const disabledAgents = Object.keys(overrides).filter(
|
||||
(name) => overrides[name].enabled === false,
|
||||
(name) => overrides[name]?.enabled === false,
|
||||
);
|
||||
|
||||
if (disabledAgents.includes(agentName)) {
|
||||
@@ -271,7 +271,7 @@ function completeAgentsToEnable(context: CommandContext, partialArg: string) {
|
||||
|
||||
const overrides = settings.merged.agents.overrides;
|
||||
const disabledAgents = Object.entries(overrides)
|
||||
.filter(([_, override]) => override.enabled === false)
|
||||
.filter(([_, override]) => override?.enabled === false)
|
||||
.map(([name]) => name);
|
||||
|
||||
return disabledAgents.filter((name) => name.startsWith(partialArg));
|
||||
@@ -291,7 +291,7 @@ function completeAllAgents(context: CommandContext, partialArg: string) {
|
||||
if (!config) return [];
|
||||
|
||||
const agentRegistry = config.getAgentRegistry();
|
||||
const allAgents = agentRegistry.getAllDiscoveredAgentNames() ?? [];
|
||||
const allAgents = agentRegistry?.getAllDiscoveredAgentNames() ?? [];
|
||||
return allAgents.filter((name: string) => name.startsWith(partialArg));
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ const authLogoutCommand: SlashCommand = {
|
||||
undefined,
|
||||
);
|
||||
// Strip thoughts from history instead of clearing completely
|
||||
context.services.config?.getGeminiClient().stripThoughtsFromHistory();
|
||||
context.services.config?.getGeminiClient()?.stripThoughtsFromHistory();
|
||||
// Return logout action to signal explicit state change
|
||||
return {
|
||||
type: 'logout',
|
||||
|
||||
@@ -54,7 +54,7 @@ export const bugCommand: SlashCommand = {
|
||||
const kittyProtocol = terminalCapabilityManager.isKittyProtocolEnabled()
|
||||
? 'Supported'
|
||||
: 'Unsupported';
|
||||
const authType = config?.getContentGeneratorConfig().authType || 'Unknown';
|
||||
const authType = config?.getContentGeneratorConfig()?.authType || 'Unknown';
|
||||
|
||||
let info = `
|
||||
* **CLI Version:** ${cliVersion}
|
||||
@@ -73,13 +73,13 @@ export const bugCommand: SlashCommand = {
|
||||
info += `* **IDE Client:** ${ideClient}\n`;
|
||||
}
|
||||
|
||||
const chat = config?.getGeminiClient().getChat();
|
||||
const chat = config?.getGeminiClient()?.getChat();
|
||||
const history = chat?.getHistory() || [];
|
||||
let historyFileMessage = '';
|
||||
let problemValue = bugDescription;
|
||||
|
||||
if (history.length > INITIAL_HISTORY_LENGTH) {
|
||||
const tempDir = config?.storage.getProjectTempDir();
|
||||
const tempDir = config?.storage?.getProjectTempDir();
|
||||
if (tempDir) {
|
||||
const historyFileName = `bug-report-history-${Date.now()}.json`;
|
||||
const historyFilePath = path.join(tempDir, historyFileName);
|
||||
|
||||
@@ -34,7 +34,7 @@ const getSavedChatTags = async (
|
||||
mtSortDesc: boolean,
|
||||
): Promise<ChatDetail[]> => {
|
||||
const cfg = context.services.config;
|
||||
const geminiDir = cfg?.storage.getProjectTempDir();
|
||||
const geminiDir = cfg?.storage?.getProjectTempDir();
|
||||
if (!geminiDir) {
|
||||
return [];
|
||||
}
|
||||
@@ -123,7 +123,7 @@ const saveCommand: SlashCommand = {
|
||||
}
|
||||
}
|
||||
|
||||
const chat = config?.getGeminiClient().getChat();
|
||||
const chat = config?.getGeminiClient()?.getChat();
|
||||
if (!chat) {
|
||||
return {
|
||||
type: 'message',
|
||||
@@ -134,7 +134,7 @@ const saveCommand: SlashCommand = {
|
||||
|
||||
const history = chat.getHistory();
|
||||
if (history.length > INITIAL_HISTORY_LENGTH) {
|
||||
const authType = config?.getContentGeneratorConfig().authType;
|
||||
const authType = config?.getContentGeneratorConfig()?.authType;
|
||||
await logger.saveCheckpoint({ history, authType }, tag);
|
||||
return {
|
||||
type: 'message',
|
||||
@@ -183,7 +183,7 @@ const resumeCommand: SlashCommand = {
|
||||
};
|
||||
}
|
||||
|
||||
const currentAuthType = config?.getContentGeneratorConfig().authType;
|
||||
const currentAuthType = config?.getContentGeneratorConfig()?.authType;
|
||||
if (
|
||||
checkpoint.authType &&
|
||||
currentAuthType &&
|
||||
@@ -296,7 +296,7 @@ const shareCommand: SlashCommand = {
|
||||
};
|
||||
}
|
||||
|
||||
const chat = context.services.config?.getGeminiClient().getChat();
|
||||
const chat = context.services.config?.getGeminiClient()?.getChat();
|
||||
if (!chat) {
|
||||
return {
|
||||
type: 'message',
|
||||
|
||||
@@ -25,7 +25,7 @@ export const clearCommand: SlashCommand = {
|
||||
const config = context.services.config;
|
||||
const chatRecordingService = context.services.config
|
||||
?.getGeminiClient()
|
||||
.getChat()
|
||||
?.getChat()
|
||||
.getChatRecordingService();
|
||||
|
||||
// Fire SessionEnd hook before clearing
|
||||
|
||||
@@ -43,7 +43,7 @@ export const compressCommand: SlashCommand = {
|
||||
const promptId = `compress-${Date.now()}`;
|
||||
const compressed = await context.services.config
|
||||
?.getGeminiClient()
|
||||
.tryCompressChat(promptId, true);
|
||||
?.tryCompressChat(promptId, true);
|
||||
if (compressed) {
|
||||
ui.addItem(
|
||||
{
|
||||
|
||||
@@ -15,7 +15,7 @@ export const copyCommand: SlashCommand = {
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
action: async (context, _args): Promise<SlashCommandActionReturn | void> => {
|
||||
const chat = context.services.config?.getGeminiClient().getChat();
|
||||
const chat = context.services.config?.getGeminiClient()?.getChat();
|
||||
const history = chat?.getHistory();
|
||||
|
||||
// Get the last message from the AI (model role)
|
||||
|
||||
@@ -236,7 +236,7 @@ async function restartAction(
|
||||
if (failures.length < extensionsToRestart.length) {
|
||||
try {
|
||||
await context.services.config?.reloadSkills();
|
||||
await context.services.config?.getAgentRegistry().reload();
|
||||
await context.services.config?.getAgentRegistry()?.reload();
|
||||
} catch (error) {
|
||||
context.ui.addItem({
|
||||
type: MessageType.ERROR,
|
||||
@@ -271,7 +271,7 @@ async function exploreAction(
|
||||
context: CommandContext,
|
||||
): Promise<SlashCommandActionReturn | void> {
|
||||
const settings = context.services.settings.merged;
|
||||
const useRegistryUI = settings.experimental.extensionRegistry;
|
||||
const useRegistryUI = settings.experimental?.extensionRegistry;
|
||||
|
||||
if (useRegistryUI) {
|
||||
const extensionManager = context.services.config?.getExtensionLoader();
|
||||
|
||||
@@ -45,7 +45,7 @@ function getIdeStatusMessage(ideClient: IdeClient): {
|
||||
};
|
||||
default: {
|
||||
let content = `🔴 Disconnected`;
|
||||
if (connection.details) {
|
||||
if (connection?.details) {
|
||||
content += `: ${connection.details}`;
|
||||
}
|
||||
return {
|
||||
@@ -107,7 +107,7 @@ async function getIdeStatusMessageWithFiles(ideClient: IdeClient): Promise<{
|
||||
};
|
||||
default: {
|
||||
let content = `🔴 Disconnected`;
|
||||
if (connection.details) {
|
||||
if (connection?.details) {
|
||||
content += `: ${connection.details}`;
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -139,7 +139,7 @@ const authCommand: SlashCommand = {
|
||||
}
|
||||
// Update the client with the new tools
|
||||
const geminiClient = config.getGeminiClient();
|
||||
if (geminiClient.isInitialized()) {
|
||||
if (geminiClient?.isInitialized()) {
|
||||
await geminiClient.setTools();
|
||||
}
|
||||
|
||||
@@ -361,7 +361,7 @@ const refreshCommand: SlashCommand = {
|
||||
|
||||
// Update the client with the new tools
|
||||
const geminiClient = config.getGeminiClient();
|
||||
if (geminiClient.isInitialized()) {
|
||||
if (geminiClient?.isInitialized()) {
|
||||
await geminiClient.setTools();
|
||||
}
|
||||
|
||||
@@ -419,9 +419,9 @@ async function handleEnableDisable(
|
||||
if (enable) {
|
||||
const settings = loadSettings();
|
||||
const result = await canLoadServer(name, {
|
||||
adminMcpEnabled: settings.merged.admin.mcp.enabled ?? true,
|
||||
allowedList: settings.merged.mcp.allowed,
|
||||
excludedList: settings.merged.mcp.excluded,
|
||||
adminMcpEnabled: settings.merged.admin?.mcp?.enabled ?? true,
|
||||
allowedList: settings.merged.mcp?.allowed,
|
||||
excludedList: settings.merged.mcp?.excluded,
|
||||
});
|
||||
if (
|
||||
!result.allowed &&
|
||||
@@ -465,7 +465,7 @@ async function handleEnableDisable(
|
||||
);
|
||||
await mcpClientManager.restart();
|
||||
}
|
||||
if (config.getGeminiClient().isInitialized())
|
||||
if (config.getGeminiClient()?.isInitialized())
|
||||
await config.getGeminiClient().setTools();
|
||||
context.ui.reloadCommands();
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ async function restoreAction(
|
||||
} else if (action.type === 'load_history' && loadHistory) {
|
||||
loadHistory(action.history);
|
||||
if (action.clientHistory) {
|
||||
config?.getGeminiClient().setHistory(action.clientHistory);
|
||||
config?.getGeminiClient()?.setHistory(action.clientHistory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ export const setupGithubCommand: SlashCommand = {
|
||||
}
|
||||
|
||||
// Get the latest release tag from GitHub
|
||||
const proxy = context.services.config?.getProxy();
|
||||
const proxy = context?.services?.config?.getProxy();
|
||||
const releaseTag = await getLatestGitHubRelease(proxy);
|
||||
const readmeUrl = `https://github.com/google-github-actions/run-gemini-cli/blob/${releaseTag}/README.md#quick-start`;
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ export const AboutBox: React.FC<AboutBoxProps> = ({
|
||||
<Text color={theme.text.primary}>{cliVersion}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
{!['N/A'].includes(GIT_COMMIT_INFO) && (
|
||||
{GIT_COMMIT_INFO && !['N/A'].includes(GIT_COMMIT_INFO) && (
|
||||
<Box flexDirection="row">
|
||||
<Box width="35%">
|
||||
<Text bold color={theme.text.link}>
|
||||
|
||||
@@ -161,25 +161,25 @@ function getFieldDefaultFromDefinition(
|
||||
return !definition.experimental; // Experimental agents default to disabled
|
||||
}
|
||||
if (field.key === 'model') {
|
||||
return definition.modelConfig.model ?? 'inherit';
|
||||
return definition.modelConfig?.model ?? 'inherit';
|
||||
}
|
||||
if (field.key === 'temperature') {
|
||||
return definition.modelConfig.generateContentConfig?.temperature;
|
||||
return definition.modelConfig?.generateContentConfig?.temperature;
|
||||
}
|
||||
if (field.key === 'topP') {
|
||||
return definition.modelConfig.generateContentConfig?.topP;
|
||||
return definition.modelConfig?.generateContentConfig?.topP;
|
||||
}
|
||||
if (field.key === 'topK') {
|
||||
return definition.modelConfig.generateContentConfig?.topK;
|
||||
return definition.modelConfig?.generateContentConfig?.topK;
|
||||
}
|
||||
if (field.key === 'maxOutputTokens') {
|
||||
return definition.modelConfig.generateContentConfig?.maxOutputTokens;
|
||||
return definition.modelConfig?.generateContentConfig?.maxOutputTokens;
|
||||
}
|
||||
if (field.key === 'maxTimeMinutes') {
|
||||
return definition.runConfig.maxTimeMinutes;
|
||||
return definition.runConfig?.maxTimeMinutes;
|
||||
}
|
||||
if (field.key === 'maxTurns') {
|
||||
return definition.runConfig.maxTurns;
|
||||
return definition.runConfig?.maxTurns;
|
||||
}
|
||||
|
||||
return field.defaultValue;
|
||||
|
||||
@@ -1083,7 +1083,7 @@ export const AskUserDialog: React.FC<AskUserDialogProps> = ({
|
||||
const currentQuestion = questions[currentQuestionIndex];
|
||||
|
||||
const effectiveQuestion = useMemo(() => {
|
||||
if (currentQuestion.type === 'yesno') {
|
||||
if (currentQuestion?.type === 'yesno') {
|
||||
return {
|
||||
...currentQuestion,
|
||||
options: [
|
||||
|
||||
@@ -427,7 +427,7 @@ export const BackgroundShellDisplay = ({
|
||||
height="100%"
|
||||
width="100%"
|
||||
borderStyle="single"
|
||||
borderColor={isFocused ? theme.ui.focus : undefined}
|
||||
borderColor={isFocused ? theme.border.focused : undefined}
|
||||
>
|
||||
<Box
|
||||
flexDirection="row"
|
||||
@@ -438,7 +438,7 @@ export const BackgroundShellDisplay = ({
|
||||
borderRight={false}
|
||||
borderTop={false}
|
||||
paddingX={1}
|
||||
borderColor={isFocused ? theme.ui.focus : undefined}
|
||||
borderColor={isFocused ? theme.border.focused : undefined}
|
||||
>
|
||||
<Box flexDirection="row">
|
||||
{renderTabs()}
|
||||
|
||||
@@ -13,7 +13,7 @@ export type SpinnerProps = ComponentProps<typeof Spinner>;
|
||||
|
||||
export const CliSpinner = (props: SpinnerProps) => {
|
||||
const settings = useSettings();
|
||||
const shouldShow = settings.merged.ui.showSpinner !== false;
|
||||
const shouldShow = settings.merged.ui?.showSpinner !== false;
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldShow) {
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { renderWithProviders } from '../../test-utils/render.js';
|
||||
import { ColorsDisplay } from './ColorsDisplay.js';
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { themeManager } from '../themes/theme-manager.js';
|
||||
import type { Theme, ColorsTheme } from '../themes/theme.js';
|
||||
import type { SemanticColors } from '../themes/semantic-tokens.js';
|
||||
|
||||
describe('ColorsDisplay', () => {
|
||||
beforeEach(() => {
|
||||
vi.spyOn(themeManager, 'getSemanticColors').mockReturnValue({
|
||||
text: {
|
||||
primary: '#ffffff',
|
||||
secondary: '#cccccc',
|
||||
link: '#0000ff',
|
||||
accent: '#ff00ff',
|
||||
response: '#ffffff',
|
||||
},
|
||||
background: {
|
||||
primary: '#000000',
|
||||
message: '#111111',
|
||||
input: '#222222',
|
||||
focus: '#333333',
|
||||
diff: {
|
||||
added: '#003300',
|
||||
removed: '#330000',
|
||||
},
|
||||
},
|
||||
border: {
|
||||
default: '#555555',
|
||||
},
|
||||
ui: {
|
||||
comment: '#666666',
|
||||
symbol: '#cccccc',
|
||||
active: '#0000ff',
|
||||
dark: '#333333',
|
||||
focus: '#0000ff',
|
||||
gradient: undefined,
|
||||
},
|
||||
status: {
|
||||
error: '#ff0000',
|
||||
success: '#00ff00',
|
||||
warning: '#ffff00',
|
||||
},
|
||||
});
|
||||
|
||||
vi.spyOn(themeManager, 'getActiveTheme').mockReturnValue({
|
||||
name: 'Test Theme',
|
||||
type: 'dark',
|
||||
colors: {} as unknown as ColorsTheme,
|
||||
semanticColors: {
|
||||
text: {
|
||||
primary: '#ffffff',
|
||||
secondary: '#cccccc',
|
||||
link: '#0000ff',
|
||||
accent: '#ff00ff',
|
||||
response: '#ffffff',
|
||||
},
|
||||
background: {
|
||||
primary: '#000000',
|
||||
message: '#111111',
|
||||
input: '#222222',
|
||||
diff: {
|
||||
added: '#003300',
|
||||
removed: '#330000',
|
||||
},
|
||||
},
|
||||
border: {
|
||||
default: '#555555',
|
||||
},
|
||||
ui: {
|
||||
comment: '#666666',
|
||||
symbol: '#cccccc',
|
||||
active: '#0000ff',
|
||||
dark: '#333333',
|
||||
focus: '#0000ff',
|
||||
gradient: undefined,
|
||||
},
|
||||
status: {
|
||||
error: '#ff0000',
|
||||
success: '#00ff00',
|
||||
warning: '#ffff00',
|
||||
},
|
||||
} as unknown as SemanticColors,
|
||||
} as unknown as Theme);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('renders correctly', async () => {
|
||||
const mockTheme = themeManager.getActiveTheme();
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
<ColorsDisplay activeTheme={mockTheme} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
const output = lastFrame();
|
||||
|
||||
// Check for title and description
|
||||
expect(output).toContain('How do colors get applied?');
|
||||
expect(output).toContain('Hex:');
|
||||
|
||||
// Check for some color names and values expect(output).toContain('text.primary');
|
||||
expect(output).toContain('#ffffff');
|
||||
expect(output).toContain('background.diff.added');
|
||||
expect(output).toContain('#003300');
|
||||
expect(output).toContain('border.default');
|
||||
expect(output).toContain('#555555');
|
||||
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
@@ -1,277 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import Gradient from 'ink-gradient';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import type { Theme } from '../themes/theme.js';
|
||||
|
||||
interface StandardColorRow {
|
||||
type: 'standard';
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface GradientColorRow {
|
||||
type: 'gradient';
|
||||
name: string;
|
||||
value: string[];
|
||||
}
|
||||
|
||||
interface BackgroundColorRow {
|
||||
type: 'background';
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
type ColorRow = StandardColorRow | GradientColorRow | BackgroundColorRow;
|
||||
|
||||
const VALUE_COLUMN_WIDTH = 10;
|
||||
|
||||
const COLOR_DESCRIPTIONS: Record<string, string> = {
|
||||
'text.primary': 'Primary text color (uses terminal default if blank)',
|
||||
'text.secondary': 'Secondary/dimmed text color',
|
||||
'text.link': 'Hyperlink and highlighting color',
|
||||
'text.accent': 'Accent color for emphasis',
|
||||
'text.response':
|
||||
'Color for model response text (uses terminal default if blank)',
|
||||
'background.primary': 'Main terminal background color',
|
||||
'background.message': 'Subtle background for message blocks',
|
||||
'background.input': 'Background for the input prompt',
|
||||
'background.focus': 'Background highlight for selected/focused items',
|
||||
'background.diff.added': 'Background for added lines in diffs',
|
||||
'background.diff.removed': 'Background for removed lines in diffs',
|
||||
'border.default': 'Standard border color',
|
||||
'ui.comment': 'Color for code comments and metadata',
|
||||
'ui.symbol': 'Color for technical symbols and UI icons',
|
||||
'ui.active': 'Border color for active or running elements',
|
||||
'ui.dark': 'Deeply dimmed color for subtle UI elements',
|
||||
'ui.focus':
|
||||
'Color for focused elements (e.g. selected menu items, focused borders)',
|
||||
'status.error': 'Color for error messages and critical status',
|
||||
'status.success': 'Color for success messages and positive status',
|
||||
'status.warning': 'Color for warnings and cautionary status',
|
||||
};
|
||||
|
||||
interface ColorsDisplayProps {
|
||||
activeTheme: Theme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines a contrasting text color (black or white) based on the background color's luminance.
|
||||
*/
|
||||
function getContrastingTextColor(hex: string): string {
|
||||
if (!hex || !hex.startsWith('#') || hex.length < 7) {
|
||||
// Fallback for invalid hex codes or named colors
|
||||
return theme.text.primary;
|
||||
}
|
||||
const r = parseInt(hex.slice(1, 3), 16);
|
||||
const g = parseInt(hex.slice(3, 5), 16);
|
||||
const b = parseInt(hex.slice(5, 7), 16);
|
||||
// Using YIQ formula to determine luminance
|
||||
const yiq = (r * 299 + g * 587 + b * 114) / 1000;
|
||||
return yiq >= 128 ? '#000000' : '#FFFFFF';
|
||||
}
|
||||
|
||||
export const ColorsDisplay: React.FC<ColorsDisplayProps> = ({
|
||||
activeTheme,
|
||||
}) => {
|
||||
const semanticColors = activeTheme.semanticColors;
|
||||
|
||||
const backgroundRows: BackgroundColorRow[] = [];
|
||||
const standardRows: StandardColorRow[] = [];
|
||||
let gradientRow: GradientColorRow | null = null;
|
||||
|
||||
if (semanticColors.ui.gradient && semanticColors.ui.gradient.length > 0) {
|
||||
gradientRow = {
|
||||
type: 'gradient',
|
||||
name: 'ui.gradient',
|
||||
value: semanticColors.ui.gradient,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively flattens the semanticColors object.
|
||||
*/
|
||||
const flattenColors = (obj: object, path: string = '') => {
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
if (value === undefined || value === null) continue;
|
||||
const newPath = path ? `${path}.${key}` : key;
|
||||
|
||||
if (key === 'gradient' && Array.isArray(value)) {
|
||||
// Gradient handled separately
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof value === 'object' && !Array.isArray(value)) {
|
||||
flattenColors(value, newPath);
|
||||
} else if (typeof value === 'string') {
|
||||
if (newPath.startsWith('background.')) {
|
||||
backgroundRows.push({
|
||||
type: 'background',
|
||||
name: newPath,
|
||||
value,
|
||||
});
|
||||
} else {
|
||||
standardRows.push({
|
||||
type: 'standard',
|
||||
name: newPath,
|
||||
value,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
flattenColors(semanticColors);
|
||||
|
||||
// Final order: Backgrounds first, then Standards, then Gradient
|
||||
const allRows: ColorRow[] = [
|
||||
...backgroundRows,
|
||||
...standardRows,
|
||||
...(gradientRow ? [gradientRow] : []),
|
||||
];
|
||||
|
||||
return (
|
||||
<Box
|
||||
flexDirection="column"
|
||||
paddingX={1}
|
||||
paddingY={0}
|
||||
borderStyle="round"
|
||||
borderColor={theme.border.default}
|
||||
>
|
||||
<Box marginBottom={1} flexDirection="column">
|
||||
<Text bold color={theme.text.accent}>
|
||||
DEVELOPER TOOLS (Not visible to users)
|
||||
</Text>
|
||||
<Box marginTop={1} flexDirection="column">
|
||||
<Text color={theme.text.primary}>
|
||||
<Text bold>How do colors get applied?</Text>
|
||||
</Text>
|
||||
<Box marginLeft={2} flexDirection="column">
|
||||
<Text color={theme.text.primary}>
|
||||
• <Text bold>Hex:</Text> Rendered exactly by modern terminals. Not
|
||||
overridden by app themes.
|
||||
</Text>
|
||||
<Text color={theme.text.primary}>
|
||||
• <Text bold>Blank:</Text> Uses your terminal's default
|
||||
foreground/background.
|
||||
</Text>
|
||||
<Text color={theme.text.primary}>
|
||||
• <Text bold>Compatibility:</Text> On older terminals, hex is
|
||||
approximated to the nearest ANSI color.
|
||||
</Text>
|
||||
<Text color={theme.text.primary}>
|
||||
• <Text bold>ANSI Names:</Text> 'red',
|
||||
'green', etc. are mapped to your terminal app's
|
||||
palette.
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Header */}
|
||||
<Box flexDirection="row" marginBottom={0} paddingX={1}>
|
||||
<Box width={VALUE_COLUMN_WIDTH}>
|
||||
<Text bold color={theme.text.link} dimColor>
|
||||
Value
|
||||
</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1}>
|
||||
<Text bold color={theme.text.link} dimColor>
|
||||
Name
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* All Rows */}
|
||||
<Box flexDirection="column">
|
||||
{allRows.map((row) => {
|
||||
if (row.type === 'standard') return renderStandardRow(row);
|
||||
if (row.type === 'gradient') return renderGradientRow(row);
|
||||
if (row.type === 'background') return renderBackgroundRow(row);
|
||||
return null;
|
||||
})}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
function renderStandardRow({ name, value }: StandardColorRow) {
|
||||
const isHex = value.startsWith('#');
|
||||
const displayColor = isHex ? value : theme.text.primary;
|
||||
const description = COLOR_DESCRIPTIONS[name] || '';
|
||||
|
||||
return (
|
||||
<Box key={name} flexDirection="row" paddingX={1}>
|
||||
<Box width={VALUE_COLUMN_WIDTH}>
|
||||
<Text color={displayColor}>{value || '(blank)'}</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1} flexDirection="row">
|
||||
<Box width="30%">
|
||||
<Text color={displayColor}>{name}</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1} paddingLeft={1}>
|
||||
<Text color={theme.text.secondary}>{description}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function renderGradientRow({ name, value }: GradientColorRow) {
|
||||
const description = COLOR_DESCRIPTIONS[name] || '';
|
||||
|
||||
return (
|
||||
<Box key={name} flexDirection="row" paddingX={1}>
|
||||
<Box width={VALUE_COLUMN_WIDTH} flexDirection="column">
|
||||
{value.map((c, i) => (
|
||||
<Text key={i} color={c}>
|
||||
{c}
|
||||
</Text>
|
||||
))}
|
||||
</Box>
|
||||
<Box flexGrow={1} flexDirection="row">
|
||||
<Box width="30%">
|
||||
<Gradient colors={value}>
|
||||
<Text>{name}</Text>
|
||||
</Gradient>
|
||||
</Box>
|
||||
<Box flexGrow={1} paddingLeft={1}>
|
||||
<Text color={theme.text.secondary}>{description}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function renderBackgroundRow({ name, value }: BackgroundColorRow) {
|
||||
const description = COLOR_DESCRIPTIONS[name] || '';
|
||||
|
||||
return (
|
||||
<Box key={name} flexDirection="row" paddingX={1}>
|
||||
<Box
|
||||
width={VALUE_COLUMN_WIDTH}
|
||||
backgroundColor={value}
|
||||
justifyContent="center"
|
||||
paddingX={1}
|
||||
>
|
||||
<Text color={getContrastingTextColor(value)} bold wrap="truncate">
|
||||
{value || 'default'}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1} flexDirection="row" paddingLeft={1}>
|
||||
<Box width="30%">
|
||||
<Text color={theme.text.primary}>{name}</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1} paddingLeft={1}>
|
||||
<Text color={theme.text.secondary}>{description}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -79,7 +79,7 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
hasPendingToolConfirmation ||
|
||||
Boolean(uiState.commandConfirmationRequest) ||
|
||||
Boolean(uiState.authConsentRequest) ||
|
||||
(uiState.confirmUpdateExtensionRequests.length ?? 0) > 0 ||
|
||||
(uiState.confirmUpdateExtensionRequests?.length ?? 0) > 0 ||
|
||||
Boolean(uiState.loopDetectionConfirmationRequest) ||
|
||||
Boolean(uiState.quota.proQuotaRequest) ||
|
||||
Boolean(uiState.quota.validationRequest) ||
|
||||
|
||||
@@ -40,7 +40,7 @@ export const DetailedMessagesDisplay: React.FC<
|
||||
if (textWidth <= 0) {
|
||||
return 1;
|
||||
}
|
||||
const lines = Math.ceil((msg.content.length || 1) / textWidth);
|
||||
const lines = Math.ceil((msg.content?.length || 1) / textWidth);
|
||||
return Math.max(1, lines);
|
||||
},
|
||||
[width, messages],
|
||||
|
||||
@@ -21,7 +21,8 @@ import { ProQuotaDialog } from './ProQuotaDialog.js';
|
||||
import { ValidationDialog } from './ValidationDialog.js';
|
||||
import { OverageMenuDialog } from './OverageMenuDialog.js';
|
||||
import { EmptyWalletDialog } from './EmptyWalletDialog.js';
|
||||
import { relaunchApp } from '../../utils/processUtils.js';
|
||||
import { runExitCleanup } from '../../utils/cleanup.js';
|
||||
import { RELAUNCH_EXIT_CODE } from '../../utils/processUtils.js';
|
||||
import { SessionBrowser } from './SessionBrowser.js';
|
||||
import { PermissionsModifyTrustDialog } from './PermissionsModifyTrustDialog.js';
|
||||
import { ModelDialog } from './ModelDialog.js';
|
||||
@@ -230,7 +231,10 @@ export const DialogManager = ({
|
||||
<Box flexDirection="column">
|
||||
<SettingsDialog
|
||||
onSelect={() => uiActions.closeSettingsDialog()}
|
||||
onRestartRequest={relaunchApp}
|
||||
onRestartRequest={async () => {
|
||||
await runExitCleanup();
|
||||
process.exit(RELAUNCH_EXIT_CODE);
|
||||
}}
|
||||
availableTerminalHeight={terminalHeight - staticExtraHeight}
|
||||
/>
|
||||
</Box>
|
||||
@@ -255,7 +259,7 @@ export const DialogManager = ({
|
||||
onClose={uiActions.closeAgentConfigDialog}
|
||||
onSave={async () => {
|
||||
// Reload agent registry to pick up changes
|
||||
const agentRegistry = config.getAgentRegistry();
|
||||
const agentRegistry = config?.getAgentRegistry();
|
||||
if (agentRegistry) {
|
||||
await agentRegistry.reload();
|
||||
}
|
||||
|
||||
@@ -246,9 +246,7 @@ describe('FolderTrustDialog', () => {
|
||||
|
||||
it('should call relaunchApp when isRestarting is true', async () => {
|
||||
vi.useFakeTimers();
|
||||
const relaunchApp = vi
|
||||
.spyOn(processUtils, 'relaunchApp')
|
||||
.mockResolvedValue(undefined);
|
||||
const relaunchApp = vi.spyOn(processUtils, 'relaunchApp');
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
<FolderTrustDialog onSelect={vi.fn()} isRestarting={true} />,
|
||||
);
|
||||
@@ -261,9 +259,7 @@ describe('FolderTrustDialog', () => {
|
||||
|
||||
it('should not call relaunchApp if unmounted before timeout', async () => {
|
||||
vi.useFakeTimers();
|
||||
const relaunchApp = vi
|
||||
.spyOn(processUtils, 'relaunchApp')
|
||||
.mockResolvedValue(undefined);
|
||||
const relaunchApp = vi.spyOn(processUtils, 'relaunchApp');
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
<FolderTrustDialog onSelect={vi.fn()} isRestarting={true} />,
|
||||
);
|
||||
|
||||
@@ -54,7 +54,9 @@ export const FolderTrustDialog: React.FC<FolderTrustDialogProps> = ({
|
||||
useEffect(() => {
|
||||
let timer: ReturnType<typeof setTimeout>;
|
||||
if (isRestarting) {
|
||||
timer = setTimeout(relaunchApp, 250);
|
||||
timer = setTimeout(async () => {
|
||||
await relaunchApp();
|
||||
}, 250);
|
||||
}
|
||||
return () => {
|
||||
if (timer) clearTimeout(timer);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user