diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx index 174510d066..72fdb0ce48 100644 --- a/packages/cli/src/ui/AppContainer.tsx +++ b/packages/cli/src/ui/AppContainer.tsx @@ -1521,6 +1521,7 @@ Logging in with Google... Restarting Gemini CLI to continue. ); await toggleDevToolsPanel( config, + showErrorDetails, () => setShowErrorDetails((prev) => !prev), () => setShowErrorDetails(true), ); @@ -1660,6 +1661,7 @@ Logging in with Google... Restarting Gemini CLI to continue. tabFocusTimeoutRef, showTransientMessage, settings.merged.general.devtools, + showErrorDetails, ], ); diff --git a/packages/cli/src/utils/devtoolsService.test.ts b/packages/cli/src/utils/devtoolsService.test.ts index 922d4d1483..cb3b907d97 100644 --- a/packages/cli/src/utils/devtoolsService.test.ts +++ b/packages/cli/src/utils/devtoolsService.test.ts @@ -437,7 +437,19 @@ describe('devtoolsService', () => { }); describe('toggleDevToolsPanel', () => { - it('calls toggle when browser opens successfully', async () => { + it('calls toggle (to close) when already open', async () => { + const config = createMockConfig(); + const toggle = vi.fn(); + const setOpen = vi.fn(); + + const promise = toggleDevToolsPanel(config, true, toggle, setOpen); + await promise; + + expect(toggle).toHaveBeenCalledTimes(1); + expect(setOpen).not.toHaveBeenCalled(); + }); + + it('does NOT call toggle or setOpen when browser opens successfully', async () => { const config = createMockConfig(); const toggle = vi.fn(); const setOpen = vi.fn(); @@ -447,18 +459,18 @@ describe('devtoolsService', () => { mockDevToolsInstance.start.mockResolvedValue('http://127.0.0.1:25417'); mockDevToolsInstance.getPort.mockReturnValue(25417); - const promise = toggleDevToolsPanel(config, toggle, setOpen); + const promise = toggleDevToolsPanel(config, false, toggle, setOpen); await vi.waitFor(() => expect(MockWebSocket.instances.length).toBe(1)); MockWebSocket.instances[0].simulateError(); await promise; - expect(toggle).toHaveBeenCalledTimes(1); + expect(toggle).not.toHaveBeenCalled(); expect(setOpen).not.toHaveBeenCalled(); }); - it('calls toggle when browser fails to open', async () => { + it('calls setOpen when browser fails to open', async () => { const config = createMockConfig(); const toggle = vi.fn(); const setOpen = vi.fn(); @@ -468,18 +480,18 @@ describe('devtoolsService', () => { mockDevToolsInstance.start.mockResolvedValue('http://127.0.0.1:25417'); mockDevToolsInstance.getPort.mockReturnValue(25417); - const promise = toggleDevToolsPanel(config, toggle, setOpen); + const promise = toggleDevToolsPanel(config, false, toggle, setOpen); await vi.waitFor(() => expect(MockWebSocket.instances.length).toBe(1)); MockWebSocket.instances[0].simulateError(); await promise; - expect(toggle).toHaveBeenCalledTimes(1); - expect(setOpen).not.toHaveBeenCalled(); + expect(toggle).not.toHaveBeenCalled(); + expect(setOpen).toHaveBeenCalledTimes(1); }); - it('calls toggle when shouldLaunchBrowser returns false', async () => { + it('calls setOpen when shouldLaunchBrowser returns false', async () => { const config = createMockConfig(); const toggle = vi.fn(); const setOpen = vi.fn(); @@ -488,15 +500,15 @@ describe('devtoolsService', () => { mockDevToolsInstance.start.mockResolvedValue('http://127.0.0.1:25417'); mockDevToolsInstance.getPort.mockReturnValue(25417); - const promise = toggleDevToolsPanel(config, toggle, setOpen); + const promise = toggleDevToolsPanel(config, false, toggle, setOpen); await vi.waitFor(() => expect(MockWebSocket.instances.length).toBe(1)); MockWebSocket.instances[0].simulateError(); await promise; - expect(toggle).toHaveBeenCalledTimes(1); - expect(setOpen).not.toHaveBeenCalled(); + expect(toggle).not.toHaveBeenCalled(); + expect(setOpen).toHaveBeenCalledTimes(1); }); it('calls setOpen when DevTools server fails to start', async () => { @@ -506,7 +518,7 @@ describe('devtoolsService', () => { mockDevToolsInstance.start.mockRejectedValue(new Error('fail')); - const promise = toggleDevToolsPanel(config, toggle, setOpen); + const promise = toggleDevToolsPanel(config, false, toggle, setOpen); await vi.waitFor(() => expect(MockWebSocket.instances.length).toBe(1)); MockWebSocket.instances[0].simulateError(); diff --git a/packages/cli/src/utils/devtoolsService.ts b/packages/cli/src/utils/devtoolsService.ts index 35abf0ec96..5e4b7710c4 100644 --- a/packages/cli/src/utils/devtoolsService.ts +++ b/packages/cli/src/utils/devtoolsService.ts @@ -212,14 +212,24 @@ async function startDevToolsServerImpl(config: Config): Promise { /** * Handles the F12 key toggle for the DevTools panel. - * Starts the DevTools server, attempts to open the browser, - * and always calls the toggle callback regardless of the outcome. + * Starts the DevTools server, attempts to open the browser. + * If the panel is already open, it closes it. + * If the panel is closed: + * - Attempts to open the browser. + * - If browser opening is successful, the panel remains closed. + * - If browser opening fails or is not possible, the panel is opened. */ export async function toggleDevToolsPanel( config: Config, + isOpen: boolean, toggle: () => void, setOpen: () => void, ): Promise { + if (isOpen) { + toggle(); + return; + } + try { const { openBrowserSecurely, shouldLaunchBrowser } = await import( '@google/gemini-cli-core' @@ -228,11 +238,14 @@ export async function toggleDevToolsPanel( if (shouldLaunchBrowser()) { try { await openBrowserSecurely(url); + // Browser opened successfully, don't open drawer. + return; } catch (e) { debugLogger.warn('Failed to open browser securely:', e); } } - toggle(); + // If we can't launch browser or it failed, open drawer. + setOpen(); } catch (e) { setOpen(); debugLogger.error('Failed to start DevTools server:', e);