@@ -102,11 +102,17 @@ export async function waitForGatewayReady({
102102 child . once ( "exit" , ( code , signal ) => {
103103 childExit = { code, signal } ;
104104 } ) ;
105+ const getChildExit = ( ) =>
106+ childExit ??
107+ ( child . exitCode != null || child . signalCode != null
108+ ? { code : child . exitCode , signal : child . signalCode }
109+ : null ) ;
105110 while ( Date . now ( ) - startedAt < readyTimeoutMs ) {
106- if ( childExit ) {
111+ const observedExit = getChildExit ( ) ;
112+ if ( observedExit ) {
107113 const stderr = await fs . readFile ( stderrPath , "utf8" ) . catch ( ( ) => "" ) ;
108114 throw new Error (
109- `gateway exited before readiness code=${ childExit . code ?? "null" } signal=${ childExit . signal ?? "null" } \n${ stderr . slice ( - 4000 ) } ` ,
115+ `gateway exited before readiness code=${ observedExit . code ?? "null" } signal=${ observedExit . signal ?? "null" } \n${ stderr . slice ( - 4000 ) } ` ,
110116 ) ;
111117 }
112118 for ( const endpoint of [ "/readyz" , "/healthz" ] ) {
@@ -144,6 +150,92 @@ async function stopGateway(child) {
144150 }
145151}
146152
153+ async function closeFileHandles ( handles ) {
154+ const results = await Promise . allSettled (
155+ handles . filter ( Boolean ) . map ( ( handle ) => handle . close ( ) ) ,
156+ ) ;
157+ const failedClose = results . find ( ( result ) => result . status === "rejected" ) ;
158+ if ( failedClose ) {
159+ throw failedClose . reason ;
160+ }
161+ }
162+
163+ export async function startGateway ( {
164+ configPath,
165+ env = process . env ,
166+ openImpl = fs . open ,
167+ port,
168+ repoRoot,
169+ spawnImpl = spawn ,
170+ stderrPath,
171+ stdoutPath,
172+ tempRoot,
173+ token,
174+ } ) {
175+ const stdout = await openImpl ( stdoutPath , "w" ) ;
176+ let stderr ;
177+ try {
178+ stderr = await openImpl ( stderrPath , "w" ) ;
179+ } catch ( error ) {
180+ try {
181+ await closeFileHandles ( [ stdout ] ) ;
182+ } catch { }
183+ throw error ;
184+ }
185+
186+ let child ;
187+ try {
188+ child = spawnImpl (
189+ "pnpm" ,
190+ [
191+ "openclaw" ,
192+ "gateway" ,
193+ "run" ,
194+ "--port" ,
195+ String ( port ) ,
196+ "--bind" ,
197+ "loopback" ,
198+ "--allow-unconfigured" ,
199+ ] ,
200+ {
201+ cwd : repoRoot ,
202+ env : {
203+ ...env ,
204+ HOME : path . join ( tempRoot , "home" ) ,
205+ XDG_CONFIG_HOME : path . join ( tempRoot , "xdg-config" ) ,
206+ XDG_DATA_HOME : path . join ( tempRoot , "xdg-data" ) ,
207+ XDG_CACHE_HOME : path . join ( tempRoot , "xdg-cache" ) ,
208+ OPENCLAW_CONFIG_PATH : configPath ,
209+ OPENCLAW_STATE_DIR : path . join ( tempRoot , "state" ) ,
210+ OPENCLAW_GATEWAY_TOKEN : token ,
211+ OPENCLAW_SKIP_BROWSER_CONTROL_SERVER : "1" ,
212+ OPENCLAW_SKIP_GMAIL_WATCHER : "1" ,
213+ OPENCLAW_SKIP_CANVAS_HOST : "1" ,
214+ OPENCLAW_NO_RESPAWN : "1" ,
215+ OPENCLAW_TEST_FAST : "1" ,
216+ } ,
217+ stdio : [ "ignore" , stdout . fd , stderr . fd ] ,
218+ } ,
219+ ) ;
220+ } catch ( error ) {
221+ try {
222+ await closeFileHandles ( [ stdout , stderr ] ) ;
223+ } catch { }
224+ throw error ;
225+ }
226+
227+ try {
228+ await closeFileHandles ( [ stdout , stderr ] ) ;
229+ } catch ( error ) {
230+ try {
231+ await stopGateway ( child ) ;
232+ } catch { }
233+ throw error ;
234+ }
235+
236+ return child ;
237+ }
238+
147239function quantile ( sorted , q ) {
148240 return sorted [ Math . min ( sorted . length - 1 , Math . max ( 0 , Math . ceil ( sorted . length * q ) - 1 ) ) ] ;
149241}
@@ -329,40 +421,15 @@ async function main() {
329421 2 ,
330422 ) } \n`,
331423 ) ;
332- const stdout = await fs . open ( stdoutPath , "w" ) ;
333- const stderr = await fs . open ( stderrPath , "w" ) ;
334- gatewayChild = spawn (
335- "pnpm" ,
336- [
337- "openclaw" ,
338- "gateway" ,
339- "run" ,
340- "--port" ,
341- String ( port ) ,
342- "--bind" ,
343- "loopback" ,
344- "--allow-unconfigured" ,
345- ] ,
346- {
347- cwd : repoRoot ,
348- env : {
349- ...process . env ,
350- HOME : path . join ( tempRoot , "home" ) ,
351- XDG_CONFIG_HOME : path . join ( tempRoot , "xdg-config" ) ,
352- XDG_DATA_HOME : path . join ( tempRoot , "xdg-data" ) ,
353- XDG_CACHE_HOME : path . join ( tempRoot , "xdg-cache" ) ,
354- OPENCLAW_CONFIG_PATH : configPath ,
355- OPENCLAW_STATE_DIR : path . join ( tempRoot , "state" ) ,
356- OPENCLAW_GATEWAY_TOKEN : token ,
357- OPENCLAW_SKIP_BROWSER_CONTROL_SERVER : "1" ,
358- OPENCLAW_SKIP_GMAIL_WATCHER : "1" ,
359- OPENCLAW_SKIP_CANVAS_HOST : "1" ,
360- OPENCLAW_NO_RESPAWN : "1" ,
361- OPENCLAW_TEST_FAST : "1" ,
362- } ,
363- stdio : [ "ignore" , stdout . fd , stderr . fd ] ,
364- } ,
365- ) ;
424+ gatewayChild = await startGateway ( {
425+ configPath,
426+ port,
427+ repoRoot,
428+ stderrPath,
429+ stdoutPath,
430+ tempRoot,
431+ token,
432+ } ) ;
366433 await waitForGatewayReady ( { child : gatewayChild , port, stderrPath } ) ;
367434
368435 const requireFromOpenClaw = createRequire ( path . join ( repoRoot , "package.json" ) ) ;
0 commit comments