From 306e12c23c1b95915d2e182cbf0a1e9634033375 Mon Sep 17 00:00:00 2001 From: Jacob Richman Date: Tue, 21 Oct 2025 19:54:51 -0700 Subject: [PATCH 01/13] Fix regression in handling shift+tab resulting in u in the input prompt. (#11634) --- .../ui/components/shared/text-buffer.test.ts | 34 +++++++++++++++++++ .../src/ui/components/shared/text-buffer.ts | 2 +- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/ui/components/shared/text-buffer.test.ts b/packages/cli/src/ui/components/shared/text-buffer.test.ts index 85555a14cf..9e56856aca 100644 --- a/packages/cli/src/ui/components/shared/text-buffer.test.ts +++ b/packages/cli/src/ui/components/shared/text-buffer.test.ts @@ -953,6 +953,40 @@ describe('useTextBuffer', () => { expect(getBufferState(result).lines).toEqual(['', '']); }); + it('should do nothing for a tab key press', () => { + const { result } = renderHook(() => + useTextBuffer({ viewport, isValidPath: () => false }), + ); + act(() => + result.current.handleInput({ + name: 'tab', + ctrl: false, + meta: false, + shift: false, + paste: false, + sequence: '\t', + }), + ); + expect(getBufferState(result).text).toBe(''); + }); + + it('should do nothing for a shift tab key press', () => { + const { result } = renderHook(() => + useTextBuffer({ viewport, isValidPath: () => false }), + ); + act(() => + result.current.handleInput({ + name: 'tab', + ctrl: false, + meta: false, + shift: true, + paste: false, + sequence: '\u001b[9;2u', + }), + ); + expect(getBufferState(result).text).toBe(''); + }); + it('should handle "Backspace" key', () => { const { result } = renderHook(() => useTextBuffer({ diff --git a/packages/cli/src/ui/components/shared/text-buffer.ts b/packages/cli/src/ui/components/shared/text-buffer.ts index 33548238f6..861017ce03 100644 --- a/packages/cli/src/ui/components/shared/text-buffer.ts +++ b/packages/cli/src/ui/components/shared/text-buffer.ts @@ -1933,7 +1933,7 @@ export function useTextBuffer({ else if (key.name === 'delete' || (key.ctrl && key.name === 'd')) del(); else if (key.ctrl && !key.shift && key.name === 'z') undo(); else if (key.ctrl && key.shift && key.name === 'z') redo(); - else if (input && !key.ctrl && !key.meta) { + else if (input && !key.ctrl && !key.meta && key.name !== 'tab') { insert(input, { paste: key.paste }); } }, From c7243997f47e0526318ac82dc2960ef43c44c4e8 Mon Sep 17 00:00:00 2001 From: Sandy Tao Date: Tue, 21 Oct 2025 20:56:29 -0700 Subject: [PATCH 02/13] fix(cli): fix flaky BaseSelectionList test (#11620) --- .../shared/BaseSelectionList.test.tsx | 68 +++++++++++-------- 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/packages/cli/src/ui/components/shared/BaseSelectionList.test.tsx b/packages/cli/src/ui/components/shared/BaseSelectionList.test.tsx index 8a4e6d24bf..0d383a8641 100644 --- a/packages/cli/src/ui/components/shared/BaseSelectionList.test.tsx +++ b/packages/cli/src/ui/components/shared/BaseSelectionList.test.tsx @@ -254,7 +254,7 @@ describe('BaseSelectionList', () => { }); }); - describe.skip('Scrolling and Pagination (maxItemsToShow)', () => { + describe('Scrolling and Pagination (maxItemsToShow)', () => { const longList = Array.from({ length: 10 }, (_, i) => ({ value: `Item ${i + 1}`, label: `Item ${i + 1}`, @@ -323,11 +323,13 @@ describe('BaseSelectionList', () => { // New visible window should be Items 2, 3, 4 (scroll offset 1). await updateActiveIndex(3); - const output = lastFrame(); - expect(output).not.toContain('Item 1'); - expect(output).toContain('Item 2'); - expect(output).toContain('Item 4'); - expect(output).not.toContain('Item 5'); + await waitFor(() => { + const output = lastFrame(); + expect(output).not.toContain('Item 1'); + expect(output).toContain('Item 2'); + expect(output).toContain('Item 4'); + expect(output).not.toContain('Item 5'); + }); }); it('should scroll up when activeIndex moves before the visible window', async () => { @@ -335,19 +337,23 @@ describe('BaseSelectionList', () => { await updateActiveIndex(4); - let output = lastFrame(); - expect(output).toContain('Item 3'); // Should see items 3, 4, 5 - expect(output).toContain('Item 5'); - expect(output).not.toContain('Item 2'); + await waitFor(() => { + const output = lastFrame(); + expect(output).toContain('Item 3'); // Should see items 3, 4, 5 + expect(output).toContain('Item 5'); + expect(output).not.toContain('Item 2'); + }); // Now test scrolling up: move to index 1 (Item 2) // This should trigger scroll up to show items 2, 3, 4 await updateActiveIndex(1); - output = lastFrame(); - expect(output).toContain('Item 2'); - expect(output).toContain('Item 4'); - expect(output).not.toContain('Item 5'); // Item 5 should no longer be visible + await waitFor(() => { + const output = lastFrame(); + expect(output).toContain('Item 2'); + expect(output).toContain('Item 4'); + expect(output).not.toContain('Item 5'); // Item 5 should no longer be visible + }); }); it('should pin the scroll offset to the end if selection starts near the end', async () => { @@ -375,16 +381,19 @@ describe('BaseSelectionList', () => { expect(lastFrame()).toContain('Item 1'); await updateActiveIndex(3); // Should trigger scroll - let output = lastFrame(); - expect(output).toContain('Item 2'); - expect(output).toContain('Item 4'); - expect(output).not.toContain('Item 1'); - + await waitFor(() => { + const output = lastFrame(); + expect(output).toContain('Item 2'); + expect(output).toContain('Item 4'); + expect(output).not.toContain('Item 1'); + }); await updateActiveIndex(5); // Scroll further - output = lastFrame(); - expect(output).toContain('Item 4'); - expect(output).toContain('Item 6'); - expect(output).not.toContain('Item 3'); + await waitFor(() => { + const output = lastFrame(); + expect(output).toContain('Item 4'); + expect(output).toContain('Item 6'); + expect(output).not.toContain('Item 3'); + }); }); it('should correctly identify the selected item within the visible window', () => { @@ -412,17 +421,16 @@ describe('BaseSelectionList', () => { expect.objectContaining({ value: 'Item 6' }), expect.objectContaining({ isSelected: true }), ); - }); - // Item 4 (index 3) should not be selected - expect(mockRenderItem).toHaveBeenCalledWith( - expect.objectContaining({ value: 'Item 4' }), - expect.objectContaining({ isSelected: false }), - ); + // Item 4 (index 3) should not be selected + expect(mockRenderItem).toHaveBeenCalledWith( + expect.objectContaining({ value: 'Item 4' }), + expect.objectContaining({ isSelected: false }), + ); + }); }); it('should handle maxItemsToShow larger than the list length', () => { - // Test edge case where maxItemsToShow exceeds available items const { lastFrame } = renderComponent( { items: longList, maxItemsToShow: 15 }, 0, From 2940b50811443d6dabeeec9b645e4930e8016ea6 Mon Sep 17 00:00:00 2001 From: Mayur Vaid <34806097+MayV@users.noreply.github.com> Date: Wed, 22 Oct 2025 09:57:49 +0530 Subject: [PATCH 03/13] fix: Ignore correct errors thrown when resizing or scrolling an exited pty (#11440) --- .../services/shellExecutionService.test.ts | 48 +++++++++++++++++++ .../src/services/shellExecutionService.ts | 6 ++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/packages/core/src/services/shellExecutionService.test.ts b/packages/core/src/services/shellExecutionService.test.ts index 45aa9fce97..372077139e 100644 --- a/packages/core/src/services/shellExecutionService.test.ts +++ b/packages/core/src/services/shellExecutionService.test.ts @@ -291,6 +291,54 @@ describe('ShellExecutionService', () => { expect(mockHeadlessTerminal.resize).toHaveBeenCalledWith(100, 40); }); + it('should not resize the pty if it is not active', async () => { + const isPtyActiveSpy = vi + .spyOn(ShellExecutionService, 'isPtyActive') + .mockReturnValue(false); + + await simulateExecution('ls -l', (pty) => { + ShellExecutionService.resizePty(pty.pid!, 100, 40); + pty.onExit.mock.calls[0][0]({ exitCode: 0, signal: null }); + }); + + expect(mockPtyProcess.resize).not.toHaveBeenCalled(); + expect(mockHeadlessTerminal.resize).not.toHaveBeenCalled(); + isPtyActiveSpy.mockRestore(); + }); + + it('should ignore errors when resizing an exited pty', async () => { + const resizeError = new Error( + 'Cannot resize a pty that has already exited', + ); + mockPtyProcess.resize.mockImplementation(() => { + throw resizeError; + }); + + // We don't expect this test to throw an error + await expect( + simulateExecution('ls -l', (pty) => { + ShellExecutionService.resizePty(pty.pid!, 100, 40); + pty.onExit.mock.calls[0][0]({ exitCode: 0, signal: null }); + }), + ).resolves.not.toThrow(); + + expect(mockPtyProcess.resize).toHaveBeenCalledWith(100, 40); + }); + + it('should re-throw other errors during resize', async () => { + const otherError = new Error('Some other error'); + mockPtyProcess.resize.mockImplementation(() => { + throw otherError; + }); + + await expect( + simulateExecution('ls -l', (pty) => { + ShellExecutionService.resizePty(pty.pid!, 100, 40); + pty.onExit.mock.calls[0][0]({ exitCode: 0, signal: null }); + }), + ).rejects.toThrow('Some other error'); + }); + it('should scroll the headless terminal', async () => { await simulateExecution('ls -l', (pty) => { pty.onData.mock.calls[0][0]('file1.txt\n'); diff --git a/packages/core/src/services/shellExecutionService.ts b/packages/core/src/services/shellExecutionService.ts index d5dc17bb14..eeec882ca8 100644 --- a/packages/core/src/services/shellExecutionService.ts +++ b/packages/core/src/services/shellExecutionService.ts @@ -750,7 +750,11 @@ export class ShellExecutionService { } catch (e) { // Ignore errors if the pty has already exited, which can happen // due to a race condition between the exit event and this call. - if (e instanceof Error && 'code' in e && e.code === 'ESRCH') { + if ( + e instanceof Error && + (('code' in e && e.code === 'ESRCH') || + e.message === 'Cannot resize a pty that has already exited') + ) { // ignore } else { throw e; From d1c913ed5c3a8579d1cf2fb2273f8f8ed5dd58ce Mon Sep 17 00:00:00 2001 From: Smetalo <92152119+Smetalo@users.noreply.github.com> Date: Wed, 22 Oct 2025 13:55:34 +0200 Subject: [PATCH 04/13] Docs: Fix broken telemetry link in docs/cli/configuration.md (#11638) --- docs/cli/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cli/configuration.md b/docs/cli/configuration.md index b9c09d26fe..23ba764d08 100644 --- a/docs/cli/configuration.md +++ b/docs/cli/configuration.md @@ -275,7 +275,7 @@ contain other project-specific files related to Gemini CLI's operation, such as: - **`telemetry`** (object) - **Description:** Configures logging and metrics collection for Gemini CLI. - For more information, see [Telemetry](../telemetry.md). + For more information, see [Telemetry](./telemetry.md). - **Default:** `{"enabled": false, "target": "local", "otlpEndpoint": "http://localhost:4317", "logPrompts": true}` - **Properties:** From 73b1afb1062a49a8d94b7db13458f099e0d770cb Mon Sep 17 00:00:00 2001 From: christine betts Date: Wed, 22 Oct 2025 10:13:42 -0400 Subject: [PATCH 05/13] Remove errant console.debug log of config (#11579) --- hello/commands/fs/grep-code.toml | 6 ++++++ hello/gemini-extension.json | 4 ++++ .../core/src/telemetry/clearcut-logger/clearcut-logger.ts | 2 -- 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 hello/commands/fs/grep-code.toml create mode 100644 hello/gemini-extension.json diff --git a/hello/commands/fs/grep-code.toml b/hello/commands/fs/grep-code.toml new file mode 100644 index 0000000000..87d957542a --- /dev/null +++ b/hello/commands/fs/grep-code.toml @@ -0,0 +1,6 @@ +prompt = """ +Please summarize the findings for the pattern `{{args}}`. + +Search Results: +!{grep -r {{args}} .} +""" diff --git a/hello/gemini-extension.json b/hello/gemini-extension.json new file mode 100644 index 0000000000..d973ab8fe4 --- /dev/null +++ b/hello/gemini-extension.json @@ -0,0 +1,4 @@ +{ + "name": "custom-commands", + "version": "1.0.0" +} diff --git a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts index cede1516ce..2ab3cf2441 100644 --- a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts +++ b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts @@ -1269,8 +1269,6 @@ export class ClearcutLogger { } getConfigJson() { - const configJson = safeJsonStringifyBooleanValuesOnly(this.config); - debugLogger.debug(configJson); return safeJsonStringifyBooleanValuesOnly(this.config); } From 0d7da7ecb184e38c6525909c67c56552b13fbf92 Mon Sep 17 00:00:00 2001 From: Mayur Vaid <34806097+MayV@users.noreply.github.com> Date: Wed, 22 Oct 2025 21:18:47 +0530 Subject: [PATCH 06/13] fix(mcp): Include path in oauth resource parameter (#11654) --- packages/core/src/mcp/oauth-utils.test.ts | 22 ++++++++++++++++++++-- packages/core/src/mcp/oauth-utils.ts | 2 +- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/packages/core/src/mcp/oauth-utils.test.ts b/packages/core/src/mcp/oauth-utils.test.ts index 710afe21aa..93aa507e21 100644 --- a/packages/core/src/mcp/oauth-utils.test.ts +++ b/packages/core/src/mcp/oauth-utils.test.ts @@ -297,14 +297,32 @@ describe('OAuthUtils', () => { const result = OAuthUtils.buildResourceParameter( 'https://example.com/oauth/token', ); - expect(result).toBe('https://example.com'); + expect(result).toBe('https://example.com/oauth/token'); }); it('should handle URLs with ports', () => { const result = OAuthUtils.buildResourceParameter( 'https://example.com:8080/oauth/token', ); - expect(result).toBe('https://example.com:8080'); + expect(result).toBe('https://example.com:8080/oauth/token'); + }); + + it('should strip query parameters from the URL', () => { + const result = OAuthUtils.buildResourceParameter( + 'https://example.com/api/v1/data?user=123&scope=read', + ); + expect(result).toBe('https://example.com/api/v1/data'); + }); + + it('should strip URL fragments from the URL', () => { + const result = OAuthUtils.buildResourceParameter( + 'https://example.com/api/v1/data#section-one', + ); + expect(result).toBe('https://example.com/api/v1/data'); + }); + + it('should throw an error for invalid URLs', () => { + expect(() => OAuthUtils.buildResourceParameter('not-a-url')).toThrow(); }); }); }); diff --git a/packages/core/src/mcp/oauth-utils.ts b/packages/core/src/mcp/oauth-utils.ts index 4787422f52..cf6bfc289d 100644 --- a/packages/core/src/mcp/oauth-utils.ts +++ b/packages/core/src/mcp/oauth-utils.ts @@ -360,6 +360,6 @@ export class OAuthUtils { */ static buildResourceParameter(endpointUrl: string): string { const url = new URL(endpointUrl); - return `${url.protocol}//${url.host}`; + return `${url.protocol}//${url.host}${url.pathname}`; } } From dc90c8fec77f1e72e70bed1c1096aedc9384ad33 Mon Sep 17 00:00:00 2001 From: Tommaso Sciortino Date: Wed, 22 Oct 2025 09:37:33 -0700 Subject: [PATCH 07/13] Updates to package-lock.json from running npm install (#11665) --- package-lock.json | 46 +++++++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index a85917a811..624af9d70c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -596,6 +596,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -619,6 +620,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -2423,6 +2425,7 @@ "integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.2", @@ -2603,6 +2606,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -2636,6 +2640,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, @@ -3004,6 +3009,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz", "integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" @@ -3037,6 +3043,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.0.1.tgz", "integrity": "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1" @@ -3089,6 +3096,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.0.1.tgz", "integrity": "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1", @@ -3804,6 +3812,7 @@ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -4315,6 +4324,7 @@ "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -4325,6 +4335,7 @@ "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.0.0" } @@ -4602,6 +4613,7 @@ "integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.35.0", "@typescript-eslint/types": "8.35.0", @@ -5369,6 +5381,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5723,8 +5736,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/array-includes": { "version": "3.1.9", @@ -7003,7 +7015,6 @@ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "license": "MIT", - "peer": true, "dependencies": { "safe-buffer": "5.2.1" }, @@ -8090,6 +8101,7 @@ "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -8679,7 +8691,6 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -8689,7 +8700,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -8699,7 +8709,6 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -8929,7 +8938,6 @@ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "license": "MIT", - "peer": true, "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", @@ -8948,7 +8956,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -8957,15 +8964,13 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/finalhandler/node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -10200,6 +10205,7 @@ "resolved": "https://registry.npmjs.org/ink/-/ink-6.2.3.tgz", "integrity": "sha512-fQkfEJjKbLXIcVWEE3MvpYSnwtbbmRsmeNDNz1pIuOFlwE+UF2gsy228J36OXKZGWJWZJKUigphBSqCNMcARtg==", "license": "MIT", + "peer": true, "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.0", "ansi-escapes": "^7.0.0", @@ -13382,8 +13388,7 @@ "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/path-type": { "version": "3.0.0", @@ -13925,6 +13930,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -13935,6 +13941,7 @@ "integrity": "sha512-cq/o30z9W2Wb4rzBefjv5fBalHU0rJGZCHAkf/RHSBWSSYwh8PlQTqqOJmgIIbBtpj27T6FIPXeomIjZtCNVqA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" @@ -13968,6 +13975,7 @@ "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -16029,6 +16037,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -16239,7 +16248,8 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/tsx": { "version": "4.20.3", @@ -16247,6 +16257,7 @@ "integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" @@ -16431,6 +16442,7 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -16712,7 +16724,6 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4.0" } @@ -16768,6 +16779,7 @@ "integrity": "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -16884,6 +16896,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -16897,6 +16910,7 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -17654,6 +17668,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -18188,6 +18203,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, From 0542de95ebd886a5d8f4856256a01ae82cb1df69 Mon Sep 17 00:00:00 2001 From: matt korwel Date: Wed, 22 Oct 2025 09:45:42 -0700 Subject: [PATCH 08/13] fix(release): Pass args to promoteNightlyVersion (#11666) Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- package.json | 6 +++--- scripts/get-release-version.js | 8 +++++++- scripts/lint.js | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 47657d3f14..c4415ffd25 100644 --- a/package.json +++ b/package.json @@ -42,9 +42,9 @@ "test:integration:sandbox:none": "cross-env GEMINI_SANDBOX=false vitest run --root ./integration-tests", "test:integration:sandbox:docker": "cross-env GEMINI_SANDBOX=docker npm run build:sandbox && cross-env GEMINI_SANDBOX=docker vitest run --root ./integration-tests", "test:integration:sandbox:podman": "cross-env GEMINI_SANDBOX=podman vitest run --root ./integration-tests", - "lint": "eslint . --ext .ts,.tsx && eslint integration-tests", - "lint:fix": "eslint . --fix && eslint integration-tests --fix", - "lint:ci": "eslint . --ext .ts,.tsx --max-warnings 0 && eslint integration-tests --max-warnings 0", + "lint": "eslint . --ext .ts,.tsx && eslint integration-tests && eslint scripts", + "lint:fix": "eslint . --fix --ext .ts,.tsx && eslint integration-tests --fix && eslint scripts --fix && npm run format", + "lint:ci": "npm run lint:all", "lint:all": "node scripts/lint.js", "format": "prettier --experimental-cli --write .", "typecheck": "npm run typecheck --workspaces --if-present", diff --git a/scripts/get-release-version.js b/scripts/get-release-version.js index 33d955b684..442eb4444d 100644 --- a/scripts/get-release-version.js +++ b/scripts/get-release-version.js @@ -424,7 +424,13 @@ export function getVersion(options = {}) { } break; case 'promote-nightly': - versionData = promoteNightlyVersion(); + versionData = promoteNightlyVersion({ args }); + // A promoted nightly version is still a nightly, so we should check for conflicts. + if (doesVersionExist({ args, version: versionData.releaseVersion })) { + throw new Error( + `Version conflict! Promoted nightly version ${versionData.releaseVersion} already exists.`, + ); + } break; case 'stable': versionData = getStableVersion(args); diff --git a/scripts/lint.js b/scripts/lint.js index 008f7cb209..a9613dfac9 100644 --- a/scripts/lint.js +++ b/scripts/lint.js @@ -142,7 +142,7 @@ export function setupLinters() { export function runESLint() { console.log('\nRunning ESLint...'); - if (!runCommand('npm run lint:ci')) { + if (!runCommand('npm run lint')) { process.exit(1); } } From 59985138f7d9f013e8fee8a63c6f64576cfdf4f1 Mon Sep 17 00:00:00 2001 From: gemini-cli-robot Date: Wed, 22 Oct 2025 10:37:25 -0700 Subject: [PATCH 09/13] chore(release): bump version to 0.12.0-nightly.20251022.0542de95 (#11672) --- package-lock.json | 60 ++++++++-------------- package.json | 4 +- packages/a2a-server/package.json | 2 +- packages/cli/package.json | 4 +- packages/core/package.json | 2 +- packages/test-utils/package.json | 2 +- packages/vscode-ide-companion/package.json | 2 +- 7 files changed, 30 insertions(+), 46 deletions(-) diff --git a/package-lock.json b/package-lock.json index 624af9d70c..ce67774cbc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@google/gemini-cli", - "version": "0.11.0-nightly.20251021.e72c00cf", + "version": "0.12.0-nightly.20251022.0542de95", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@google/gemini-cli", - "version": "0.11.0-nightly.20251021.e72c00cf", + "version": "0.12.0-nightly.20251022.0542de95", "workspaces": [ "packages/*" ], @@ -596,7 +596,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -620,7 +619,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -2425,7 +2423,6 @@ "integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.2", @@ -2606,7 +2603,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=8.0.0" } @@ -2640,7 +2636,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, @@ -3009,7 +3004,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz", "integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" @@ -3043,7 +3037,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.0.1.tgz", "integrity": "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1" @@ -3096,7 +3089,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.0.1.tgz", "integrity": "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1", @@ -3812,7 +3804,6 @@ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -4324,7 +4315,6 @@ "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -4335,7 +4325,6 @@ "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.0.0" } @@ -4613,7 +4602,6 @@ "integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.35.0", "@typescript-eslint/types": "8.35.0", @@ -5381,7 +5369,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5736,7 +5723,8 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/array-includes": { "version": "3.1.9", @@ -7015,6 +7003,7 @@ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "license": "MIT", + "peer": true, "dependencies": { "safe-buffer": "5.2.1" }, @@ -8101,7 +8090,6 @@ "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -8691,6 +8679,7 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -8700,6 +8689,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", + "peer": true, "dependencies": { "ms": "2.0.0" } @@ -8709,6 +8699,7 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } @@ -8938,6 +8929,7 @@ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "license": "MIT", + "peer": true, "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", @@ -8956,6 +8948,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", + "peer": true, "dependencies": { "ms": "2.0.0" } @@ -8964,13 +8957,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/finalhandler/node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } @@ -10205,7 +10200,6 @@ "resolved": "https://registry.npmjs.org/ink/-/ink-6.2.3.tgz", "integrity": "sha512-fQkfEJjKbLXIcVWEE3MvpYSnwtbbmRsmeNDNz1pIuOFlwE+UF2gsy228J36OXKZGWJWZJKUigphBSqCNMcARtg==", "license": "MIT", - "peer": true, "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.0", "ansi-escapes": "^7.0.0", @@ -13388,7 +13382,8 @@ "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/path-type": { "version": "3.0.0", @@ -13930,7 +13925,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -13941,7 +13935,6 @@ "integrity": "sha512-cq/o30z9W2Wb4rzBefjv5fBalHU0rJGZCHAkf/RHSBWSSYwh8PlQTqqOJmgIIbBtpj27T6FIPXeomIjZtCNVqA==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" @@ -13975,7 +13968,6 @@ "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -16037,7 +16029,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -16248,8 +16239,7 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/tsx": { "version": "4.20.3", @@ -16257,7 +16247,6 @@ "integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" @@ -16442,7 +16431,6 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -16724,6 +16712,7 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.4.0" } @@ -16779,7 +16768,6 @@ "integrity": "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -16896,7 +16884,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -16910,7 +16897,6 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -17668,7 +17654,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -17684,7 +17669,7 @@ }, "packages/a2a-server": { "name": "@google/gemini-cli-a2a-server", - "version": "0.11.0-nightly.20251021.e72c00cf", + "version": "0.12.0-nightly.20251022.0542de95", "dependencies": { "@a2a-js/sdk": "^0.3.2", "@google-cloud/storage": "^7.16.0", @@ -17958,7 +17943,7 @@ }, "packages/cli": { "name": "@google/gemini-cli", - "version": "0.11.0-nightly.20251021.e72c00cf", + "version": "0.12.0-nightly.20251022.0542de95", "dependencies": { "@google/gemini-cli-core": "file:../core", "@google/genai": "1.16.0", @@ -18071,7 +18056,7 @@ }, "packages/core": { "name": "@google/gemini-cli-core", - "version": "0.11.0-nightly.20251021.e72c00cf", + "version": "0.12.0-nightly.20251022.0542de95", "dependencies": { "@google-cloud/logging": "^11.2.1", "@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.21.0", @@ -18203,7 +18188,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -18213,7 +18197,7 @@ }, "packages/test-utils": { "name": "@google/gemini-cli-test-utils", - "version": "0.11.0-nightly.20251021.e72c00cf", + "version": "0.12.0-nightly.20251022.0542de95", "license": "Apache-2.0", "devDependencies": { "typescript": "^5.3.3" @@ -18224,7 +18208,7 @@ }, "packages/vscode-ide-companion": { "name": "gemini-cli-vscode-ide-companion", - "version": "0.11.0-nightly.20251021.e72c00cf", + "version": "0.12.0-nightly.20251022.0542de95", "license": "LICENSE", "dependencies": { "@modelcontextprotocol/sdk": "^1.15.1", diff --git a/package.json b/package.json index c4415ffd25..d972533488 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@google/gemini-cli", - "version": "0.11.0-nightly.20251021.e72c00cf", + "version": "0.12.0-nightly.20251022.0542de95", "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.11.0-nightly.20251021.e72c00cf" + "sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.12.0-nightly.20251022.0542de95" }, "scripts": { "start": "cross-env NODE_ENV=development node scripts/start.js", diff --git a/packages/a2a-server/package.json b/packages/a2a-server/package.json index 9594bc8f9d..ec897db298 100644 --- a/packages/a2a-server/package.json +++ b/packages/a2a-server/package.json @@ -1,6 +1,6 @@ { "name": "@google/gemini-cli-a2a-server", - "version": "0.11.0-nightly.20251021.e72c00cf", + "version": "0.12.0-nightly.20251022.0542de95", "description": "Gemini CLI A2A Server", "repository": { "type": "git", diff --git a/packages/cli/package.json b/packages/cli/package.json index 1362b95188..1087f409e2 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@google/gemini-cli", - "version": "0.11.0-nightly.20251021.e72c00cf", + "version": "0.12.0-nightly.20251022.0542de95", "description": "Gemini CLI", "repository": { "type": "git", @@ -25,7 +25,7 @@ "dist" ], "config": { - "sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.11.0-nightly.20251021.e72c00cf" + "sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.12.0-nightly.20251022.0542de95" }, "dependencies": { "@google/gemini-cli-core": "file:../core", diff --git a/packages/core/package.json b/packages/core/package.json index edd640bc43..312b41a099 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@google/gemini-cli-core", - "version": "0.11.0-nightly.20251021.e72c00cf", + "version": "0.12.0-nightly.20251022.0542de95", "description": "Gemini CLI Core", "repository": { "type": "git", diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index ac0c39bc56..7c3e5ba2d2 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@google/gemini-cli-test-utils", - "version": "0.11.0-nightly.20251021.e72c00cf", + "version": "0.12.0-nightly.20251022.0542de95", "private": true, "main": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/vscode-ide-companion/package.json b/packages/vscode-ide-companion/package.json index 4a1bc82438..82c54e1601 100644 --- a/packages/vscode-ide-companion/package.json +++ b/packages/vscode-ide-companion/package.json @@ -2,7 +2,7 @@ "name": "gemini-cli-vscode-ide-companion", "displayName": "Gemini CLI Companion", "description": "Enable Gemini CLI with direct access to your IDE workspace.", - "version": "0.11.0-nightly.20251021.e72c00cf", + "version": "0.12.0-nightly.20251022.0542de95", "publisher": "google", "icon": "assets/icon.png", "repository": { From ce655436ef97535247daa9d27f572c1ca3ed62ac Mon Sep 17 00:00:00 2001 From: Sandy Tao Date: Wed, 22 Oct 2025 10:45:50 -0700 Subject: [PATCH 10/13] fix(test): unskip and fix useToolScheduler tests (#11671) --- .../cli/src/ui/hooks/useToolScheduler.test.ts | 85 +++++++------------ 1 file changed, 31 insertions(+), 54 deletions(-) diff --git a/packages/cli/src/ui/hooks/useToolScheduler.test.ts b/packages/cli/src/ui/hooks/useToolScheduler.test.ts index 9304666246..9fd31b89f9 100644 --- a/packages/cli/src/ui/hooks/useToolScheduler.test.ts +++ b/packages/cli/src/ui/hooks/useToolScheduler.test.ts @@ -32,7 +32,6 @@ import { ApprovalMode, MockTool, } from '@google/gemini-cli-core'; -import type { HistoryItemWithoutId, HistoryItemToolGroup } from '../types.js'; import { ToolCallStatus } from '../types.js'; // Mocks @@ -101,11 +100,9 @@ const mockToolRequiresConfirmation = new MockTool({ describe('useReactToolScheduler in YOLO Mode', () => { let onComplete: Mock; - let setPendingHistoryItem: Mock; beforeEach(() => { onComplete = vi.fn(); - setPendingHistoryItem = vi.fn(); mockToolRegistry.getTool.mockClear(); (mockToolRequiresConfirmation.execute as Mock).mockClear(); (mockToolRequiresConfirmation.shouldConfirmExecute as Mock).mockClear(); @@ -128,7 +125,7 @@ describe('useReactToolScheduler in YOLO Mode', () => { useReactToolScheduler( onComplete, mockConfig as unknown as Config, - setPendingHistoryItem, + () => undefined, () => {}, ), ); @@ -187,26 +184,11 @@ describe('useReactToolScheduler in YOLO Mode', () => { }), }), ]); - - // Ensure no confirmation UI was triggered (setPendingHistoryItem should not have been called with confirmation details) - const setPendingHistoryItemCalls = setPendingHistoryItem.mock.calls; - const confirmationCall = setPendingHistoryItemCalls.find((call) => { - const item = typeof call[0] === 'function' ? call[0]({}) : call[0]; - return item?.tools?.[0]?.confirmationDetails; - }); - expect(confirmationCall).toBeUndefined(); }); }); describe('useReactToolScheduler', () => { - // TODO(ntaylormullen): The following tests are skipped due to difficulties in - // reliably testing the asynchronous state updates and interactions with timers. - // These tests involve complex sequences of events, including confirmations, - // live output updates, and cancellations, which are challenging to assert - // correctly with the current testing setup. Further investigation is needed - // to find a robust way to test these scenarios. let onComplete: Mock; - let setPendingHistoryItem: Mock; let capturedOnConfirmForTest: | ((outcome: ToolConfirmationOutcome) => void | Promise) | undefined; @@ -214,29 +196,6 @@ describe('useReactToolScheduler', () => { beforeEach(() => { onComplete = vi.fn(); capturedOnConfirmForTest = undefined; - setPendingHistoryItem = vi.fn((updaterOrValue) => { - let pendingItem: HistoryItemWithoutId | null = null; - if (typeof updaterOrValue === 'function') { - // Loosen the type for prevState to allow for more flexible updates in tests - const prevState: Partial = { - type: 'tool_group', // Still default to tool_group for most cases - tools: [], - }; - - pendingItem = updaterOrValue(prevState as any); // Allow any for more flexibility - } else { - pendingItem = updaterOrValue; - } - // Capture onConfirm if it exists, regardless of the exact type of pendingItem - // This is a common pattern in these tests. - if ( - (pendingItem as HistoryItemToolGroup)?.tools?.[0]?.confirmationDetails - ?.onConfirm - ) { - capturedOnConfirmForTest = (pendingItem as HistoryItemToolGroup) - .tools[0].confirmationDetails?.onConfirm; - } - }); mockToolRegistry.getTool.mockClear(); (mockTool.execute as Mock).mockClear(); @@ -273,7 +232,7 @@ describe('useReactToolScheduler', () => { useReactToolScheduler( onComplete, mockConfig as unknown as Config, - setPendingHistoryItem, + () => undefined, () => {}, ), ); @@ -448,7 +407,7 @@ describe('useReactToolScheduler', () => { expect(result.current[0]).toEqual([]); }); - it.skip('should handle tool requiring confirmation - approved', async () => { + it('should handle tool requiring confirmation - approved', async () => { mockToolRegistry.getTool.mockReturnValue(mockToolRequiresConfirmation); const expectedOutput = 'Confirmed output'; (mockToolRequiresConfirmation.execute as Mock).mockResolvedValue({ @@ -471,7 +430,9 @@ describe('useReactToolScheduler', () => { await vi.runAllTimersAsync(); }); - expect(setPendingHistoryItem).toHaveBeenCalled(); + const waitingCall = result.current[0][0] as any; + expect(waitingCall.status).toBe('awaiting_approval'); + capturedOnConfirmForTest = waitingCall.confirmationDetails?.onConfirm; expect(capturedOnConfirmForTest).toBeDefined(); await act(async () => { @@ -510,7 +471,7 @@ describe('useReactToolScheduler', () => { ]); }); - it.skip('should handle tool requiring confirmation - cancelled by user', async () => { + it('should handle tool requiring confirmation - cancelled by user', async () => { mockToolRegistry.getTool.mockReturnValue(mockToolRequiresConfirmation); const { result } = renderScheduler(); const schedule = result.current[1]; @@ -527,7 +488,9 @@ describe('useReactToolScheduler', () => { await vi.runAllTimersAsync(); }); - expect(setPendingHistoryItem).toHaveBeenCalled(); + const waitingCall = result.current[0][0] as any; + expect(waitingCall.status).toBe('awaiting_approval'); + capturedOnConfirmForTest = waitingCall.confirmationDetails?.onConfirm; expect(capturedOnConfirmForTest).toBeDefined(); await act(async () => { @@ -552,7 +515,8 @@ describe('useReactToolScheduler', () => { expect.objectContaining({ functionResponse: expect.objectContaining({ response: expect.objectContaining({ - error: `User did not allow tool call ${request.name}. Reason: User cancelled.`, + error: + '[Operation Cancelled] Reason: User did not allow tool call', }), }), }), @@ -562,7 +526,7 @@ describe('useReactToolScheduler', () => { ]); }); - it.skip('should handle live output updates', async () => { + it('should handle live output updates', async () => { mockToolRegistry.getTool.mockReturnValue(mockToolWithLiveOutput); let liveUpdateFn: ((output: string) => void) | undefined; let resolveExecutePromise: (value: ToolResult) => void; @@ -600,7 +564,7 @@ describe('useReactToolScheduler', () => { }); expect(liveUpdateFn).toBeDefined(); - expect(setPendingHistoryItem).toHaveBeenCalled(); + expect(result.current[0][0].status).toBe('executing'); await act(async () => { liveUpdateFn?.('Live output 1'); @@ -742,7 +706,7 @@ describe('useReactToolScheduler', () => { expect(result.current[0]).toEqual([]); }); - it.skip('should throw error if scheduling while already running', async () => { + it('should queue if scheduling while already running', async () => { mockToolRegistry.getTool.mockReturnValue(mockTool); const longExecutePromise = new Promise((resolve) => setTimeout( @@ -777,9 +741,7 @@ describe('useReactToolScheduler', () => { await vi.runAllTimersAsync(); }); - expect(() => schedule(request2, new AbortController().signal)).toThrow( - 'Cannot schedule tool calls while other tool calls are running', - ); + schedule(request2, new AbortController().signal); await act(async () => { await vi.advanceTimersByTimeAsync(50); @@ -795,6 +757,21 @@ describe('useReactToolScheduler', () => { response: expect.objectContaining({ resultDisplay: 'done display' }), }), ]); + // Wait for request2 to complete + await act(async () => { + await vi.advanceTimersByTimeAsync(50); + await vi.runAllTimersAsync(); + await act(async () => { + await vi.runAllTimersAsync(); + }); + }); + expect(onComplete).toHaveBeenCalledWith([ + expect.objectContaining({ + status: 'success', + request: request2, + response: expect.objectContaining({ resultDisplay: 'done display' }), + }), + ]); expect(result.current[0]).toEqual([]); }); }); From 0bf2a0353d55f4f94119a45519fbde00d806e717 Mon Sep 17 00:00:00 2001 From: kevinjwang1 Date: Wed, 22 Oct 2025 18:24:32 +0000 Subject: [PATCH 11/13] Add extension alias for extensions command (#11622) --- packages/cli/src/commands/extensions.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli/src/commands/extensions.tsx b/packages/cli/src/commands/extensions.tsx index e198a1ac47..be4d7160cc 100644 --- a/packages/cli/src/commands/extensions.tsx +++ b/packages/cli/src/commands/extensions.tsx @@ -16,6 +16,7 @@ import { newCommand } from './extensions/new.js'; export const extensionsCommand: CommandModule = { command: 'extensions ', + aliases: ['extension'], describe: 'Manage Gemini CLI extensions.', builder: (yargs) => yargs From 5bb9cd1a13a9bfab3a8b491e093e789de0fd032d Mon Sep 17 00:00:00 2001 From: shishu314 Date: Wed, 22 Oct 2025 14:41:26 -0400 Subject: [PATCH 12/13] feat(infra) - Create a workflow for deflake (#11535) Co-authored-by: gemini-cli-robot --- .github/workflows/deflake.yml | 142 +++++++++++++++++++++++++++++++++- docs/integration-tests.md | 10 ++- package.json | 4 +- 3 files changed, 149 insertions(+), 7 deletions(-) diff --git a/.github/workflows/deflake.yml b/.github/workflows/deflake.yml index 3d9e9a3fe8..c9f4c3d59f 100644 --- a/.github/workflows/deflake.yml +++ b/.github/workflows/deflake.yml @@ -24,13 +24,147 @@ concurrency: ${{ github.ref != 'refs/heads/main' && !startsWith(github.ref, 'refs/heads/release/') }} jobs: - deflake: - name: 'Deflake' + deflake_e2e_linux: + name: 'E2E Test (Linux) - ${{ matrix.sandbox }}' runs-on: 'gemini-cli-ubuntu-16-core' strategy: fail-fast: false + matrix: + sandbox: + - 'sandbox:none' + - 'sandbox:docker' + node-version: + - '20.x' + steps: - - name: 'Deflake' + - name: 'Checkout' + uses: 'actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955' # ratchet:actions/checkout@v5 + with: + ref: '${{ github.event.pull_request.head.sha }}' + repository: '${{ github.repository }}' + + - name: 'Set up Node.js ${{ matrix.node-version }}' + uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions-node@v4 + with: + node-version: '${{ matrix.node-version }}' + + - name: 'Install dependencies' + run: 'npm ci' + + - name: 'Build project' + run: 'npm run build' + + - name: 'Set up Docker' + if: "matrix.sandbox == 'sandbox:docker'" + uses: 'docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435' # ratchet:docker/setup-buildx-action@v3 + + - name: 'Run E2E tests' + env: + GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}' + IS_DOCKER: "${{ matrix.sandbox == 'sandbox:docker' }}" + KEEP_OUTPUT: 'true' + RUNS: '${{ github.event.inputs.runs }}' + TEST_NAME_PATTERN: '${{ github.event.inputs.test_name_pattern }}' + VERBOSE: 'true' shell: 'bash' run: | - ECHO 'DEFLAKE WORKFLOW' + if [[ "${{ env.IS_DOCKER }}" == "true" ]]; then + npm run deflake:test:integration:sandbox:docker -- --runs="${{ env.RUNS }}" -- --testNamePattern "'${{ env.TEST_NAME_PATTERN }}'" + else + npm run deflake:test:integration:sandbox:none -- --runs="${{ env.RUNS }}" -- --testNamePattern "'${{ env.TEST_NAME_PATTERN }}'" + fi + + deflake_e2e_mac: + name: 'E2E Test (macOS)' + runs-on: 'macos-latest' + steps: + - name: 'Checkout' + uses: 'actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955' # ratchet:actions/checkout@v5 + with: + ref: '${{ github.event.pull_request.head.sha }}' + repository: '${{ github.repository }}' + + - name: 'Set up Node.js 20.x' + uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions-node@v4 + with: + node-version: '20.x' + + - name: 'Install dependencies' + run: 'npm ci' + + - name: 'Build project' + run: 'npm run build' + + - name: 'Fix rollup optional dependencies on macOS' + if: "runner.os == 'macOS'" + run: | + npm cache clean --force + - name: 'Run E2E tests (non-Windows)' + if: "runner.os != 'Windows'" + env: + GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}' + KEEP_OUTPUT: 'true' + RUNS: '${{ github.event.inputs.runs }}' + SANDBOX: 'sandbox:none' + TEST_NAME_PATTERN: '${{ github.event.inputs.test_name_pattern }}' + VERBOSE: 'true' + run: | + npm run deflake:test:integration:sandbox:none -- --runs="${{ env.RUNS }}" -- --testNamePattern "'${{ env.TEST_NAME_PATTERN }}'" + + deflake_e2e_windows: + name: 'Slow E2E - Win' + runs-on: 'gemini-cli-windows-16-core' + + steps: + - name: 'Checkout' + uses: 'actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955' # ratchet:actions/checkout@v5 + with: + ref: '${{ github.event.pull_request.head.sha }}' + repository: '${{ github.repository }}' + + - name: 'Set up Node.js 20.x' + uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions-node@v4 + with: + node-version: '20.x' + cache: 'npm' + + - name: 'Configure Windows Defender exclusions' + run: | + Add-MpPreference -ExclusionPath $env:GITHUB_WORKSPACE -Force + Add-MpPreference -ExclusionPath "$env:GITHUB_WORKSPACE\node_modules" -Force + Add-MpPreference -ExclusionPath "$env:GITHUB_WORKSPACE\packages" -Force + Add-MpPreference -ExclusionPath "$env:TEMP" -Force + shell: 'pwsh' + + - name: 'Configure npm for Windows performance' + run: | + npm config set progress false + npm config set audit false + npm config set fund false + npm config set loglevel error + npm config set maxsockets 32 + npm config set registry https://registry.npmjs.org/ + shell: 'pwsh' + + - name: 'Install dependencies' + run: 'npm ci' + shell: 'pwsh' + + - name: 'Build project' + run: 'npm run build' + shell: 'pwsh' + + - name: 'Run E2E tests' + env: + GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}' + KEEP_OUTPUT: 'true' + SANDBOX: 'sandbox:none' + VERBOSE: 'true' + NODE_OPTIONS: '--max-old-space-size=32768 --max-semi-space-size=256' + UV_THREADPOOL_SIZE: '32' + NODE_ENV: 'test' + RUNS: '${{ github.event.inputs.runs }}' + TEST_NAME_PATTERN: '${{ github.event.inputs.test_name_pattern }}' + shell: 'pwsh' + run: | + npm run deflake:test:integration:sandbox:none -- --runs="${{ env.RUNS }}" -- --testNamePattern "'${{ env.TEST_NAME_PATTERN }}'" diff --git a/docs/integration-tests.md b/docs/integration-tests.md index b6b8c1ec51..24377c1934 100644 --- a/docs/integration-tests.md +++ b/docs/integration-tests.md @@ -59,12 +59,20 @@ npm run test:e2e -- --test-name-pattern "reads a file" ### Deflaking a test Before adding a **new** integration test, you should test it at least 5 times -with the deflake script to make sure that it is not flaky. +with the deflake script or workflow to make sure that it is not flaky. + +### Deflake script ```bash npm run deflake -- --runs=5 --command="npm run test:e2e -- -- --test-name-pattern ''" ``` +#### Deflake Workflow + +```bash +gh workflow run deflake.yml --ref -f test_name_pattern="" +``` + ### Running all tests To run the entire suite of integration tests, use the following command: diff --git a/package.json b/package.json index d972533488..416d83d0e0 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,8 @@ "start:a2a-server": "CODER_AGENT_PORT=41242 npm run start --workspace @google/gemini-cli-a2a-server", "debug": "cross-env DEBUG=1 node --inspect-brk scripts/start.js", "deflake": "node scripts/deflake.js", - "deflake:test:integration:sandbox:none": "npm run deflake -- --command=\"npm run test:integration:sandbox:none -- --retry=0", - "deflake:test:integration:sandbox:docker": "npm run deflake -- --command=\"npm run test:integration:sandbox:docker -- --retry=0", + "deflake:test:integration:sandbox:none": "npm run deflake -- --command=\"npm run test:integration:sandbox:none -- --retry=0\"", + "deflake:test:integration:sandbox:docker": "npm run deflake -- --command=\"npm run test:integration:sandbox:docker -- --retry=0\"", "auth:npm": "npx google-artifactregistry-auth", "auth:docker": "gcloud auth configure-docker us-west1-docker.pkg.dev", "auth": "npm run auth:npm && npm run auth:docker", From 6d75005afc3517cd00d3bea766f0e8ff146a0859 Mon Sep 17 00:00:00 2001 From: Adib234 <30782825+Adib234@users.noreply.github.com> Date: Wed, 22 Oct 2025 11:57:10 -0700 Subject: [PATCH 13/13] Add setting to disable YOLO mode (#11609) Co-authored-by: Shreya Keshive --- docs/cli/enterprise.md | 20 +++++++++ .../file-system-interactive.test.ts | 11 ++++- packages/cli/src/config/config.test.ts | 17 ++++++++ packages/cli/src/config/config.ts | 16 ++++++++ packages/cli/src/config/settings.test.ts | 34 +++++++++++++++ packages/cli/src/config/settingsSchema.ts | 9 ++++ .../ui/hooks/useAutoAcceptIndicator.test.ts | 41 +++++++++++++++++++ .../src/ui/hooks/useAutoAcceptIndicator.ts | 15 +++++++ packages/core/src/config/config.test.ts | 34 +++++++++++++++ packages/core/src/config/config.ts | 7 ++++ 10 files changed, 203 insertions(+), 1 deletion(-) diff --git a/docs/cli/enterprise.md b/docs/cli/enterprise.md index 0274e48f00..fb641b82f0 100644 --- a/docs/cli/enterprise.md +++ b/docs/cli/enterprise.md @@ -202,6 +202,26 @@ allowlisting with `coreTools`, as it relies on blocking known-bad commands, and clever users may find ways to bypass simple string-based blocks. **Allowlisting is the recommended approach.** +### Disabling YOLO Mode + +To ensure that users cannot bypass the confirmation prompt for tool execution, +you can disable YOLO mode at the policy level. This adds a critical layer of +safety, as it prevents the model from executing tools without explicit user +approval. + +**Example:** Force all tool executions to require user confirmation. + +```json +{ + "security": { + "disableYoloMode": true + } +} +``` + +This setting is highly recommended in an enterprise environment to prevent +unintended tool execution. + ## Managing Custom Tools (MCP Servers) If your organization uses custom tools via diff --git a/integration-tests/file-system-interactive.test.ts b/integration-tests/file-system-interactive.test.ts index 2dec6c593c..d7ad73fd0d 100644 --- a/integration-tests/file-system-interactive.test.ts +++ b/integration-tests/file-system-interactive.test.ts @@ -20,7 +20,16 @@ describe('Interactive file system', () => { it('should perform a read-then-write sequence', async () => { const fileName = 'version.txt'; - await rig.setup('interactive-read-then-write'); + await rig.setup('interactive-read-then-write', { + settings: { + security: { + auth: { + selectedType: 'gemini-api-key', + }, + disableYoloMode: false, + }, + }, + }); rig.createFile(fileName, '1.0.0'); const run = await rig.runInteractive(); diff --git a/packages/cli/src/config/config.test.ts b/packages/cli/src/config/config.test.ts index 5490afb678..b935d4a696 100644 --- a/packages/cli/src/config/config.test.ts +++ b/packages/cli/src/config/config.test.ts @@ -1112,6 +1112,23 @@ describe('Approval mode tool exclusion logic', () => { expect(excludedTools).not.toContain(WRITE_FILE_TOOL_NAME); // Should be allowed in auto_edit }); + it('should throw an error if YOLO mode is attempted when disableYoloMode is true', async () => { + process.argv = ['node', 'script.js', '--yolo']; + const argv = await parseArguments({} as Settings); + const settings: Settings = { + security: { + disableYoloMode: true, + }, + }; + const extensions: GeminiCLIExtension[] = []; + + await expect( + loadCliConfig(settings, extensions, 'test-session', argv), + ).rejects.toThrow( + 'Cannot start in YOLO mode when it is disabled by settings', + ); + }); + it('should throw an error for invalid approval mode values in loadCliConfig', async () => { // Create a mock argv with an invalid approval mode that bypasses argument parsing validation const invalidArgv: Partial & { approvalMode: string } = { diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index 1bb1a3b70d..96ab5c04d1 100755 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -442,6 +442,21 @@ export async function loadCliConfig( argv.yolo || false ? ApprovalMode.YOLO : ApprovalMode.DEFAULT; } + // Override approval mode if disableYoloMode is set. + if (settings.security?.disableYoloMode) { + if (approvalMode === ApprovalMode.YOLO) { + debugLogger.error('YOLO mode is disabled by the "disableYolo" setting.'); + throw new FatalConfigError( + 'Cannot start in YOLO mode when it is disabled by settings', + ); + } + approvalMode = ApprovalMode.DEFAULT; + } else if (approvalMode === ApprovalMode.YOLO) { + debugLogger.warn( + 'YOLO mode is enabled. All tool calls will be automatically approved.', + ); + } + // Force approval mode to default if the folder is not trusted. if (!trustedFolder && approvalMode !== ApprovalMode.DEFAULT) { debugLogger.warn( @@ -583,6 +598,7 @@ export async function loadCliConfig( geminiMdFileCount: fileCount, geminiMdFilePaths: filePaths, approvalMode, + disableYoloMode: settings.security?.disableYoloMode, showMemoryUsage: settings.ui?.showMemoryUsage || false, accessibility: { ...settings.ui?.accessibility, diff --git a/packages/cli/src/config/settings.test.ts b/packages/cli/src/config/settings.test.ts index 672f42bd5b..c3b7cf748e 100644 --- a/packages/cli/src/config/settings.test.ts +++ b/packages/cli/src/config/settings.test.ts @@ -609,6 +609,40 @@ describe('Settings Loading and Merging', () => { expect(settings.merged.security?.folderTrust?.enabled).toBe(true); // System setting should be used }); + it('should not allow user or workspace to override system disableYoloMode', () => { + (mockFsExistsSync as Mock).mockReturnValue(true); + const userSettingsContent = { + security: { + disableYoloMode: false, + }, + }; + const workspaceSettingsContent = { + security: { + disableYoloMode: false, // This should be ignored + }, + }; + const systemSettingsContent = { + security: { + disableYoloMode: true, + }, + }; + + (fs.readFileSync as Mock).mockImplementation( + (p: fs.PathOrFileDescriptor) => { + if (p === getSystemSettingsPath()) + return JSON.stringify(systemSettingsContent); + if (p === USER_SETTINGS_PATH) + return JSON.stringify(userSettingsContent); + if (p === MOCK_WORKSPACE_SETTINGS_PATH) + return JSON.stringify(workspaceSettingsContent); + return '{}'; + }, + ); + + const settings = loadSettings(MOCK_WORKSPACE_DIR); + expect(settings.merged.security?.disableYoloMode).toBe(true); // System setting should be used + }); + it('should handle contextFileName correctly when only in user settings', () => { (mockFsExistsSync as Mock).mockImplementation( (p: fs.PathLike) => p === USER_SETTINGS_PATH, diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index c01e691f44..2c3fc21ff4 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -937,6 +937,15 @@ const SETTINGS_SCHEMA = { description: 'Security-related settings.', showInDialog: false, properties: { + disableYoloMode: { + type: 'boolean', + label: 'Disable YOLO Mode', + category: 'Security', + requiresRestart: true, + default: false, + description: 'Disable YOLO mode, even if enabled by a flag.', + showInDialog: true, + }, folderTrust: { type: 'object', label: 'Folder Trust', diff --git a/packages/cli/src/ui/hooks/useAutoAcceptIndicator.test.ts b/packages/cli/src/ui/hooks/useAutoAcceptIndicator.test.ts index 8ad0e7546f..2e103ca234 100644 --- a/packages/cli/src/ui/hooks/useAutoAcceptIndicator.test.ts +++ b/packages/cli/src/ui/hooks/useAutoAcceptIndicator.test.ts @@ -37,6 +37,7 @@ vi.mock('@google/gemini-cli-core', async () => { interface MockConfigInstanceShape { getApprovalMode: Mock<() => ApprovalMode>; setApprovalMode: Mock<(value: ApprovalMode) => void>; + isYoloModeDisabled: Mock<() => boolean>; isTrustedFolder: Mock<() => boolean>; getCoreTools: Mock<() => string[]>; getToolDiscoveryCommand: Mock<() => string | undefined>; @@ -76,6 +77,7 @@ describe('useAutoAcceptIndicator', () => { setApprovalMode: instanceSetApprovalModeMock as Mock< (value: ApprovalMode) => void >, + isYoloModeDisabled: vi.fn().mockReturnValue(false), isTrustedFolder: vi.fn().mockReturnValue(true) as Mock<() => boolean>, getCoreTools: vi.fn().mockReturnValue([]) as Mock<() => string[]>, getToolDiscoveryCommand: vi.fn().mockReturnValue(undefined) as Mock< @@ -471,6 +473,45 @@ describe('useAutoAcceptIndicator', () => { }); }); + describe('when YOLO mode is disabled by settings', () => { + beforeEach(() => { + // Ensure isYoloModeDisabled returns true for these tests + if (mockConfigInstance && mockConfigInstance.isYoloModeDisabled) { + mockConfigInstance.isYoloModeDisabled.mockReturnValue(true); + } + }); + + it('should not enable YOLO mode when Ctrl+Y is pressed and add an info message', () => { + mockConfigInstance.getApprovalMode.mockReturnValue(ApprovalMode.DEFAULT); + const mockAddItem = vi.fn(); + const { result } = renderHook(() => + useAutoAcceptIndicator({ + config: mockConfigInstance as unknown as ActualConfigType, + addItem: mockAddItem, + }), + ); + + expect(result.current).toBe(ApprovalMode.DEFAULT); + + act(() => { + capturedUseKeypressHandler({ name: 'y', ctrl: true } as Key); + }); + + // setApprovalMode should not be called because the check should return early + expect(mockConfigInstance.setApprovalMode).not.toHaveBeenCalled(); + // An info message should be added + expect(mockAddItem).toHaveBeenCalledWith( + { + type: MessageType.WARNING, + text: 'You cannot enter YOLO mode since it is disabled in your settings.', + }, + expect.any(Number), + ); + // The mode should not change + expect(result.current).toBe(ApprovalMode.DEFAULT); + }); + }); + it('should call onApprovalModeChange when switching to YOLO mode', () => { mockConfigInstance.getApprovalMode.mockReturnValue(ApprovalMode.DEFAULT); diff --git a/packages/cli/src/ui/hooks/useAutoAcceptIndicator.ts b/packages/cli/src/ui/hooks/useAutoAcceptIndicator.ts index ae749a4648..6091420abd 100644 --- a/packages/cli/src/ui/hooks/useAutoAcceptIndicator.ts +++ b/packages/cli/src/ui/hooks/useAutoAcceptIndicator.ts @@ -34,6 +34,21 @@ export function useAutoAcceptIndicator({ let nextApprovalMode: ApprovalMode | undefined; if (key.ctrl && key.name === 'y') { + if ( + config.isYoloModeDisabled() && + config.getApprovalMode() !== ApprovalMode.YOLO + ) { + if (addItem) { + addItem( + { + type: MessageType.WARNING, + text: 'You cannot enter YOLO mode since it is disabled in your settings.', + }, + Date.now(), + ); + } + return; + } nextApprovalMode = config.getApprovalMode() === ApprovalMode.YOLO ? ApprovalMode.DEFAULT diff --git a/packages/core/src/config/config.test.ts b/packages/core/src/config/config.test.ts index 0546f7cba6..d1549c5355 100644 --- a/packages/core/src/config/config.test.ts +++ b/packages/core/src/config/config.test.ts @@ -995,6 +995,40 @@ describe('setApprovalMode with folder trust', () => { }); }); +describe('isYoloModeDisabled', () => { + const baseParams: ConfigParameters = { + sessionId: 'test', + targetDir: '.', + debugMode: false, + model: 'test-model', + cwd: '.', + }; + + it('should return false when yolo mode is not disabled and folder is trusted', () => { + const config = new Config(baseParams); + vi.spyOn(config, 'isTrustedFolder').mockReturnValue(true); + expect(config.isYoloModeDisabled()).toBe(false); + }); + + it('should return true when yolo mode is disabled by parameter', () => { + const config = new Config({ ...baseParams, disableYoloMode: true }); + vi.spyOn(config, 'isTrustedFolder').mockReturnValue(true); + expect(config.isYoloModeDisabled()).toBe(true); + }); + + it('should return true when folder is untrusted', () => { + const config = new Config(baseParams); + vi.spyOn(config, 'isTrustedFolder').mockReturnValue(false); + expect(config.isYoloModeDisabled()).toBe(true); + }); + + it('should return true when yolo is disabled and folder is untrusted', () => { + const config = new Config({ ...baseParams, disableYoloMode: true }); + vi.spyOn(config, 'isTrustedFolder').mockReturnValue(false); + expect(config.isYoloModeDisabled()).toBe(true); + }); +}); + describe('BaseLlmClient Lifecycle', () => { const MODEL = 'gemini-pro'; const SANDBOX: SandboxConfig = { diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index baa8bc6175..e6e859ee00 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -284,6 +284,7 @@ export interface ConfigParameters { retryFetchErrors?: boolean; enableShellOutputEfficiency?: boolean; ptyInfo?: string; + disableYoloMode?: boolean; } export class Config { @@ -380,6 +381,7 @@ export class Config { private readonly continueOnFailedApiCall: boolean; private readonly retryFetchErrors: boolean; private readonly enableShellOutputEfficiency: boolean; + private readonly disableYoloMode: boolean; constructor(params: ConfigParameters) { this.sessionId = params.sessionId; @@ -496,6 +498,7 @@ export class Config { format: params.output?.format ?? OutputFormat.TEXT, }; this.retryFetchErrors = params.retryFetchErrors ?? false; + this.disableYoloMode = params.disableYoloMode ?? false; if (params.contextFileName) { setGeminiMdFilename(params.contextFileName); @@ -761,6 +764,10 @@ export class Config { this.approvalMode = mode; } + isYoloModeDisabled(): boolean { + return this.disableYoloMode || !this.isTrustedFolder(); + } + getShowMemoryUsage(): boolean { return this.showMemoryUsage; }