@@ -54,6 +54,20 @@ function jsonResponse(res, status, data) {
5454function sleep ( ms ) {
5555 return new Promise ( resolve => setTimeout ( resolve , ms ) ) ;
5656}
57+ function parseTimeoutValue ( val , label , fallback ) {
58+ if ( val === undefined ) {
59+ return fallback ;
60+ }
61+ const parsed = typeof val === 'number' ? val : parseInt ( String ( val ) , 10 ) ;
62+ if ( Number . isNaN ( parsed ) || parsed <= 0 ) {
63+ console . error ( `[serve] Invalid ${ label } ="${ val } ", using default ${ fallback } s` ) ;
64+ return fallback ;
65+ }
66+ return parsed ;
67+ }
68+ function parseEnvTimeout ( envVar , fallback ) {
69+ return parseTimeoutValue ( process . env [ envVar ] , envVar , fallback ) ;
70+ }
5771// ─── DOM helpers ─────────────────────────────────────────────────────
5872/**
5973 * Click the 'New Conversation' button to reset context.
@@ -267,41 +281,65 @@ async function waitForReply(page, beforeText, opts = {}) {
267281 let lastText = beforeText ;
268282 let stableCount = 0 ;
269283 const stableThreshold = 4 ; // 4 * 500ms = 2s of stability fallback
284+ let reconnectCount = 0 ;
270285 while ( Date . now ( ) < deadline ) {
271- const generating = await isGenerating ( page ) ;
272- const currentText = await getConversationText ( page ) ;
273- const textChanged = currentText !== beforeText && currentText . length > 0 ;
274- if ( generating ) {
275- hasStartedGenerating = true ;
276- stableCount = 0 ; // Reset stability while generating
277- }
278- else {
279- if ( hasStartedGenerating ) {
280- // It actively generated and now it stopped -> DONE
281- // Provide a small buffer to let React render the final message fully
282- await sleep ( 500 ) ;
283- return ;
286+ try {
287+ const generating = await isGenerating ( page ) ;
288+ const currentText = await getConversationText ( page ) ;
289+ const textChanged = currentText !== beforeText && currentText . length > 0 ;
290+ if ( generating ) {
291+ hasStartedGenerating = true ;
292+ stableCount = 0 ; // Reset stability while generating
284293 }
285- // Fallback: If it never showed "Generating/Cancel", but text changed and is stable
286- if ( textChanged ) {
287- if ( currentText === lastText ) {
288- stableCount ++ ;
289- if ( stableCount >= stableThreshold ) {
290- return ; // Text has been stable for 2 seconds -> DONE
294+ else {
295+ if ( hasStartedGenerating ) {
296+ // It actively generated and now it stopped -> DONE
297+ // Provide a small buffer to let React render the final message fully
298+ await sleep ( 500 ) ;
299+ return page ;
300+ }
301+ // Fallback: If it never showed "Generating/Cancel", but text changed and is stable
302+ if ( textChanged ) {
303+ if ( currentText === lastText ) {
304+ stableCount ++ ;
305+ if ( stableCount >= stableThreshold ) {
306+ return page ; // Text has been stable for 2 seconds -> DONE
307+ }
308+ }
309+ else {
310+ stableCount = 0 ;
311+ lastText = currentText ;
291312 }
292313 }
293- else {
314+ }
315+ }
316+ catch ( err ) {
317+ const msg = err . message || String ( err ) ;
318+ const isSessionLoss = / c l o s e d | l o s t | n o t o p e n | w e b s o c k e t / i. test ( msg ) ;
319+ if ( opts . reconnect && isSessionLoss && reconnectCount < 2 ) {
320+ reconnectCount ++ ;
321+ console . error ( `[serve] CDP session loss detected (${ msg } ), attempting to reconnect (${ reconnectCount } /2)...` ) ;
322+ try {
323+ page = await opts . reconnect ( ) ;
324+ // Reset stability tracking after reconnect
294325 stableCount = 0 ;
295- lastText = currentText ;
326+ lastText = beforeText ;
327+ continue ;
328+ }
329+ catch ( reconnectErr ) {
330+ console . error ( `[serve] Reconnection failed: ${ reconnectErr . message } ` ) ;
331+ throw err ; // Throw original error if reconnection itself fails
296332 }
297333 }
334+ throw err ;
298335 }
299336 await sleep ( pollInterval ) ;
300337 }
301- throw new Error ( ' Timeout waiting for Antigravity reply' ) ;
338+ throw new Error ( ` Timeout waiting for Antigravity reply after ${ timeout / 1000 } s` ) ;
302339}
303340// ─── Request Handlers ────────────────────────────────────────────────
304- async function handleMessages ( body , page , bridge ) {
341+ async function handleMessages ( body , page , opts = { } ) {
342+ const { bridge, timeout, reconnect } = opts ;
305343 // Extract the last user message
306344 const userMessages = body . messages . filter ( m => m . role === 'user' ) ;
307345 if ( userMessages . length === 0 ) {
@@ -328,7 +366,7 @@ async function handleMessages(body, page, bridge) {
328366 await sendMessage ( page , userText , bridge ) ;
329367 // Poll for reply (change detection)
330368 console . error ( '[serve] Waiting for reply...' ) ;
331- await waitForReply ( page , beforeText ) ;
369+ page = await waitForReply ( page , beforeText , { timeout , reconnect } ) ;
332370 // Extract the actual reply text precisely from the DOM
333371 const replyText = await getLastAssistantReply ( page , userText ) ;
334372 console . error ( `[serve] Got reply: "${ replyText . slice ( 0 , 80 ) } ${ replyText . length > 80 ? '...' : '' } "` ) ;
@@ -349,6 +387,10 @@ async function handleMessages(body, page, bridge) {
349387// ─── Server ──────────────────────────────────────────────────────────
350388export async function startServe ( opts = { } ) {
351389 const port = opts . port ?? 8082 ;
390+ const envTimeoutSeconds = parseEnvTimeout ( 'OPENCLI_ANTIGRAVITY_TIMEOUT' , 120 ) ;
391+ const effectiveTimeoutSeconds = parseTimeoutValue ( opts . timeout , '--timeout' , envTimeoutSeconds ) ;
392+ const effectiveTimeout = effectiveTimeoutSeconds * 1000 ;
393+ console . error ( `[serve] Starting Antigravity API proxy on port ${ port } (timeout: ${ effectiveTimeout / 1000 } s)` ) ;
352394 // Lazy CDP connection — connect when first request comes in
353395 let cdp = null ;
354396 let page = null ;
@@ -462,7 +504,11 @@ export async function startServe(opts = {}) {
462504 }
463505 // Lazy connect on first request
464506 const activePage = await ensureConnected ( ) ;
465- const response = await handleMessages ( body , activePage , cdp ?? undefined ) ;
507+ const response = await handleMessages ( body , activePage , {
508+ bridge : cdp ,
509+ timeout : effectiveTimeout ,
510+ reconnect : ensureConnected ,
511+ } ) ;
466512 jsonResponse ( res , 200 , response ) ;
467513 }
468514 finally {
0 commit comments