66 * SPDX-License-Identifier: Apache-2.0
77 */
88
9- import { main } from './src/gemini.js ' ;
10- import { FatalError , writeToStderr } from '@google/gemini-cli-core ' ;
11- import { runExitCleanup } from './src/utils/cleanup.js ' ;
9+ import { spawn } from 'node:child_process ' ;
10+ import os from 'node:os ' ;
11+ import v8 from 'node:v8 ' ;
1212
1313// --- Global Entry Point ---
1414
@@ -20,52 +20,145 @@ process.on('uncaughtException', (error) => {
2020 error instanceof Error &&
2121 error . message === 'Cannot resize a pty that has already exited'
2222 ) {
23- // This error happens on Windows with node-pty when resizing a pty that has just exited.
24- // It is a race condition in node-pty that we cannot prevent, so we silence it.
2523 return ;
2624 }
2725
28- // For other errors, we rely on the default behavior, but since we attached a listener,
29- // we must manually replicate it.
3026 if ( error instanceof Error ) {
31- writeToStderr ( error . stack + '\n' ) ;
27+ process . stderr . write ( error . stack + '\n' ) ;
3228 } else {
33- writeToStderr ( String ( error ) + '\n' ) ;
29+ process . stderr . write ( String ( error ) + '\n' ) ;
3430 }
3531 process . exit ( 1 ) ;
3632} ) ;
3733
38- main ( ) . catch ( async ( error ) => {
39- // Set a timeout to force exit if cleanup hangs
40- const cleanupTimeout = setTimeout ( ( ) => {
41- writeToStderr ( 'Cleanup timed out, forcing exit...\n' ) ;
42- process . exit ( 1 ) ;
43- } , 5000 ) ;
44-
45- try {
46- await runExitCleanup ( ) ;
47- } catch ( cleanupError ) {
48- writeToStderr (
49- `Error during final cleanup: ${ cleanupError instanceof Error ? cleanupError . message : String ( cleanupError ) } \n` ,
50- ) ;
51- } finally {
52- clearTimeout ( cleanupTimeout ) ;
53- }
34+ async function run ( ) {
35+ if ( ! process . env [ 'GEMINI_CLI_NO_RELAUNCH' ] && ! process . env [ 'SANDBOX' ] ) {
36+ // --- Lightweight Parent Process / Daemon ---
37+ // We avoid importing heavy dependencies here to save ~1.5s of startup time.
38+
39+ // Check memory arguments (lightweight version)
40+ const nodeArgs : string [ ] = [ ...process . execArgv ] ;
41+ const scriptArgs = process . argv . slice ( 2 ) ;
42+ const isCommand =
43+ scriptArgs . length > 0 &&
44+ [
45+ 'mcp' ,
46+ 'extensions' ,
47+ 'extension' ,
48+ 'skills' ,
49+ 'skill' ,
50+ 'hooks' ,
51+ 'hook' ,
52+ ] . includes ( scriptArgs [ 0 ] ) ;
5453
55- if ( error instanceof FatalError ) {
56- let errorMessage = error . message ;
57- if ( ! process . env [ 'NO_COLOR' ] ) {
58- errorMessage = `\x1b[31m${ errorMessage } \x1b[0m` ;
54+ if ( ! isCommand ) {
55+ // Very rudimentary check for memory args without parsing full settings.
56+ // If we need more memory, we just provide it.
57+ const totalMemoryMB = os . totalmem ( ) / ( 1024 * 1024 ) ;
58+ const heapStats = v8 . getHeapStatistics ( ) ;
59+ const currentMaxOldSpaceSizeMb = Math . floor (
60+ heapStats . heap_size_limit / 1024 / 1024 ,
61+ ) ;
62+ const targetMaxOldSpaceSizeInMB = Math . floor ( totalMemoryMB * 0.5 ) ;
63+
64+ if ( targetMaxOldSpaceSizeInMB > currentMaxOldSpaceSizeMb ) {
65+ nodeArgs . push ( `--max-old-space-size=${ targetMaxOldSpaceSizeInMB } ` ) ;
66+ }
5967 }
60- writeToStderr ( errorMessage + '\n' ) ;
61- process . exit ( error . exitCode ) ;
62- }
6368
64- writeToStderr ( 'An unexpected critical error occurred:' ) ;
65- if ( error instanceof Error ) {
66- writeToStderr ( error . stack + '\n' ) ;
69+ const script = process . argv [ 1 ] ;
70+ nodeArgs . push ( script ) ;
71+ nodeArgs . push ( ...scriptArgs ) ;
72+
73+ const newEnv = { ...process . env , GEMINI_CLI_NO_RELAUNCH : 'true' } ;
74+ const RELAUNCH_EXIT_CODE = 199 ;
75+ let latestAdminSettings : unknown = undefined ;
76+
77+ const runner = ( ) => {
78+ process . stdin . pause ( ) ;
79+
80+ const child = spawn ( process . execPath , nodeArgs , {
81+ stdio : [ 'inherit' , 'inherit' , 'inherit' , 'ipc' ] ,
82+ env : newEnv ,
83+ } ) ;
84+
85+ if ( latestAdminSettings ) {
86+ child . send ( { type : 'admin-settings' , settings : latestAdminSettings } ) ;
87+ }
88+
89+ child . on ( 'message' , ( msg : { type ?: string ; settings ?: unknown } ) => {
90+ if ( msg . type === 'admin-settings-update' && msg . settings ) {
91+ latestAdminSettings = msg . settings ;
92+ }
93+ } ) ;
94+
95+ return new Promise < number > ( ( resolve ) => {
96+ child . on ( 'error' , ( ) => resolve ( 1 ) ) ;
97+ child . on ( 'close' , ( code ) => {
98+ process . stdin . resume ( ) ;
99+ resolve ( code ?? 1 ) ;
100+ } ) ;
101+ } ) ;
102+ } ;
103+
104+ while ( true ) {
105+ try {
106+ const exitCode = await runner ( ) ;
107+ if ( exitCode !== RELAUNCH_EXIT_CODE ) {
108+ process . exit ( exitCode ) ;
109+ }
110+ } catch ( error : unknown ) {
111+ process . stdin . resume ( ) ;
112+ process . stderr . write (
113+ `Fatal error: Failed to relaunch the CLI process.\n${ error instanceof Error ? ( error . stack ?? error . message ) : String ( error ) } \n` ,
114+ ) ;
115+ process . exit ( 1 ) ;
116+ }
117+ }
67118 } else {
68- writeToStderr ( String ( error ) + '\n' ) ;
119+ // --- Heavy Child Process ---
120+ // Now we can safely import everything.
121+ const { main } = await import ( './src/gemini.js' ) ;
122+ const { FatalError, writeToStderr } = await import (
123+ '@google/gemini-cli-core'
124+ ) ;
125+ const { runExitCleanup } = await import ( './src/utils/cleanup.js' ) ;
126+
127+ main ( ) . catch ( async ( error : unknown ) => {
128+ // Set a timeout to force exit if cleanup hangs
129+ const cleanupTimeout = setTimeout ( ( ) => {
130+ writeToStderr ( 'Cleanup timed out, forcing exit...\n' ) ;
131+ process . exit ( 1 ) ;
132+ } , 5000 ) ;
133+
134+ try {
135+ await runExitCleanup ( ) ;
136+ } catch ( cleanupError : unknown ) {
137+ writeToStderr (
138+ `Error during final cleanup: ${ cleanupError instanceof Error ? cleanupError . message : String ( cleanupError ) } \n` ,
139+ ) ;
140+ } finally {
141+ clearTimeout ( cleanupTimeout ) ;
142+ }
143+
144+ if ( error instanceof FatalError ) {
145+ let errorMessage = error . message ;
146+ if ( ! process . env [ 'NO_COLOR' ] ) {
147+ errorMessage = `\x1b[31m${ errorMessage } \x1b[0m` ;
148+ }
149+ writeToStderr ( errorMessage + '\n' ) ;
150+ process . exit ( error . exitCode ) ;
151+ }
152+
153+ writeToStderr ( 'An unexpected critical error occurred:' ) ;
154+ if ( error instanceof Error ) {
155+ writeToStderr ( error . stack + '\n' ) ;
156+ } else {
157+ writeToStderr ( String ( error ) + '\n' ) ;
158+ }
159+ process . exit ( 1 ) ;
160+ } ) ;
69161 }
70- process . exit ( 1 ) ;
71- } ) ;
162+ }
163+
164+ run ( ) ;
0 commit comments