From 9c0a6864daa22fb8122df322b6bbd9fb2c519ed1 Mon Sep 17 00:00:00 2001 From: Sandy Tao Date: Wed, 22 Apr 2026 15:21:58 -0700 Subject: [PATCH] fix(devtools): reduce memory usage and defer connection (#24496) --- packages/cli/src/gemini.tsx | 2 +- packages/cli/src/nonInteractiveCli.ts | 2 +- .../cli/src/utils/devtoolsService.test.ts | 63 +++---------------- packages/cli/src/utils/devtoolsService.ts | 28 +-------- packages/devtools/src/index.ts | 2 +- 5 files changed, 14 insertions(+), 83 deletions(-) diff --git a/packages/cli/src/gemini.tsx b/packages/cli/src/gemini.tsx index 6e257270d7..e55b005946 100644 --- a/packages/cli/src/gemini.tsx +++ b/packages/cli/src/gemini.tsx @@ -532,7 +532,7 @@ export async function main() { const { setupInitialActivityLogger } = await import( './utils/devtoolsService.js' ); - await setupInitialActivityLogger(config); + setupInitialActivityLogger(config); } // Register config for telemetry shutdown diff --git a/packages/cli/src/nonInteractiveCli.ts b/packages/cli/src/nonInteractiveCli.ts index dc5255edee..8c1c2ca6a2 100644 --- a/packages/cli/src/nonInteractiveCli.ts +++ b/packages/cli/src/nonInteractiveCli.ts @@ -80,7 +80,7 @@ export async function runNonInteractive( const { setupInitialActivityLogger } = await import( './utils/devtoolsService.js' ); - await setupInitialActivityLogger(config); + setupInitialActivityLogger(config); } const { stdout: workingStdout } = createWorkingStdio(); diff --git a/packages/cli/src/utils/devtoolsService.test.ts b/packages/cli/src/utils/devtoolsService.test.ts index 981d121ffe..5280b7e354 100644 --- a/packages/cli/src/utils/devtoolsService.test.ts +++ b/packages/cli/src/utils/devtoolsService.test.ts @@ -123,69 +123,22 @@ describe('devtoolsService', () => { }); describe('setupInitialActivityLogger', () => { - it('stays in buffer mode when no existing server found', async () => { + it('stays in buffer mode (no probe attempted)', () => { const config = createMockConfig(); - const promise = setupInitialActivityLogger(config); - - // Probe fires immediately — no server running - await vi.waitFor(() => expect(MockWebSocket.instances.length).toBe(1)); - MockWebSocket.instances[0].simulateError(); - - await promise; + setupInitialActivityLogger(config); expect(mockInitActivityLogger).toHaveBeenCalledWith(config, { mode: 'buffer', }); expect(mockAddNetworkTransport).not.toHaveBeenCalled(); + // No WebSocket probe on startup + expect(MockWebSocket.instances.length).toBe(0); }); - it('attaches transport when existing server found at startup', async () => { - const config = createMockConfig(); - const promise = setupInitialActivityLogger(config); - - await vi.waitFor(() => expect(MockWebSocket.instances.length).toBe(1)); - MockWebSocket.instances[0].simulateOpen(); - - await promise; - - expect(mockInitActivityLogger).toHaveBeenCalledWith(config, { - mode: 'buffer', - }); - expect(mockAddNetworkTransport).toHaveBeenCalledWith( - config, - '127.0.0.1', - 25417, - expect.any(Function), - ); - expect( - mockActivityLoggerInstance.enableNetworkLogging, - ).toHaveBeenCalled(); - }); - - it('F12 short-circuits when startup already connected', async () => { - const config = createMockConfig(); - - // Startup: probe succeeds - const setupPromise = setupInitialActivityLogger(config); - await vi.waitFor(() => expect(MockWebSocket.instances.length).toBe(1)); - MockWebSocket.instances[0].simulateOpen(); - await setupPromise; - - mockAddNetworkTransport.mockClear(); - mockActivityLoggerInstance.enableNetworkLogging.mockClear(); - - // F12: should return URL immediately - const url = await startDevToolsServer(config); - - expect(url).toBe('http://localhost:25417'); - expect(mockAddNetworkTransport).not.toHaveBeenCalled(); - expect(mockDevToolsInstance.start).not.toHaveBeenCalled(); - }); - - it('initializes in file mode when target env var is set', async () => { + it('initializes in file mode when target env var is set', () => { process.env['GEMINI_CLI_ACTIVITY_LOG_TARGET'] = '/tmp/test.jsonl'; const config = createMockConfig(); - await setupInitialActivityLogger(config); + setupInitialActivityLogger(config); expect(mockInitActivityLogger).toHaveBeenCalledWith(config, { mode: 'file', @@ -195,10 +148,10 @@ describe('devtoolsService', () => { expect(MockWebSocket.instances.length).toBe(0); }); - it('does nothing in file mode when config.storage is missing', async () => { + it('does nothing in file mode when config.storage is missing', () => { process.env['GEMINI_CLI_ACTIVITY_LOG_TARGET'] = '/tmp/test.jsonl'; const config = createMockConfig({ storage: undefined }); - await setupInitialActivityLogger(config); + setupInitialActivityLogger(config); expect(mockInitActivityLogger).not.toHaveBeenCalled(); expect(MockWebSocket.instances.length).toBe(0); diff --git a/packages/cli/src/utils/devtoolsService.ts b/packages/cli/src/utils/devtoolsService.ts index 448e2acb80..4f54738875 100644 --- a/packages/cli/src/utils/devtoolsService.ts +++ b/packages/cli/src/utils/devtoolsService.ts @@ -116,39 +116,17 @@ async function handlePromotion(config: Config) { /** * Initializes the activity logger. * Interception starts immediately in buffering mode. - * If an existing DevTools server is found, attaches transport eagerly. + * Transport is only attached when the user presses F12. */ -export async function setupInitialActivityLogger(config: Config) { +export function setupInitialActivityLogger(config: Config) { const target = process.env['GEMINI_CLI_ACTIVITY_LOG_TARGET']; if (target) { if (!config.storage) return; initActivityLogger(config, { mode: 'file', filePath: target }); } else { - // Start in buffering mode (no transport attached yet) + // Start in buffering mode — transport attached later on F12 initActivityLogger(config, { mode: 'buffer' }); - - // Eagerly probe for an existing DevTools server - try { - const existing = await probeDevTools( - DEFAULT_DEVTOOLS_HOST, - DEFAULT_DEVTOOLS_PORT, - ); - if (existing) { - const onReconnectFailed = () => handlePromotion(config); - addNetworkTransport( - config, - DEFAULT_DEVTOOLS_HOST, - DEFAULT_DEVTOOLS_PORT, - onReconnectFailed, - ); - ActivityLogger.getInstance().enableNetworkLogging(); - connectedUrl = `http://localhost:${DEFAULT_DEVTOOLS_PORT}`; - debugLogger.log(`DevTools (existing) at startup: ${connectedUrl}`); - } - } catch { - // Probe failed silently — stay in buffer mode - } } } diff --git a/packages/devtools/src/index.ts b/packages/devtools/src/index.ts index 73c16406de..bd33b2642a 100644 --- a/packages/devtools/src/index.ts +++ b/packages/devtools/src/index.ts @@ -124,7 +124,7 @@ export class DevTools extends EventEmitter { chunks: payload.chunk ? [payload.chunk] : undefined, } as NetworkLog; this.logs.push(entry); - if (this.logs.length > 2000) this.logs.shift(); + if (this.logs.length > 10) this.logs.shift(); this.emit('update', entry); } }