@@ -25,6 +25,8 @@ import {
2525 type BskRunner ,
2626 bskInstallMessage ,
2727 isCommandNotFound ,
28+ isNavigationLifecycleTimeoutPayload ,
29+ type ParseBskJsonOptions ,
2830 parseBskJson ,
2931 runWithSessionBusyRetry ,
3032} from "./runner" ;
@@ -88,6 +90,7 @@ async function runBsk(
8890 observeSession ?: string ,
8991 runnerTimeoutMs ?: number ,
9092 initializing = false ,
93+ parseOptions : ParseBskJsonOptions = { } ,
9194) : Promise < unknown > {
9295 const releaseForeground =
9396 observeSession !== undefined ? deps . observation . acquireForeground ( observeSession ) : undefined ;
@@ -133,7 +136,7 @@ async function runBsk(
133136 error . name = "AbortError" ;
134137 throw error ;
135138 }
136- return parseBskJson ( result , label ) ;
139+ return parseBskJson ( result , label , parseOptions ) ;
137140 } catch ( error ) {
138141 actionError = error instanceof Error ? error . message . split ( "\n" ) [ 0 ] : String ( error ) ;
139142 throw error ;
@@ -213,6 +216,8 @@ function defineBrowserOperations(deps: ToolDeps, register: DefinitionRegistrar):
213216 browserInstanceId : { type : "string" , required : true } ,
214217 url : { type : "string" } ,
215218 device : { type : "string" } ,
219+ reached : { type : "string" } ,
220+ warning : { type : "string" } ,
216221 } ,
217222 } ,
218223 render : ( _args , value ) => [
@@ -221,7 +226,10 @@ function defineBrowserOperations(deps: ToolDeps, register: DefinitionRegistrar):
221226 text :
222227 `started browser session ${ value . sessionId } ` +
223228 ( value . url !== undefined ? ` and navigated to ${ value . url } ` : "" ) +
224- ( value . device !== undefined ? ` (device: ${ value . device } )` : "" ) ,
229+ ( value . device !== undefined ? ` (device: ${ value . device } )` : "" ) +
230+ ( value . reached === "timeout"
231+ ? " — initial navigation timed out before the requested lifecycle phase; session is usable"
232+ : "" ) ,
225233 } ,
226234 ] ,
227235 } ,
@@ -266,28 +274,77 @@ function defineBrowserOperations(deps: ToolDeps, register: DefinitionRegistrar):
266274 true ,
267275 ) ;
268276 }
277+ let navigationTimeout :
278+ | {
279+ reached : "timeout" ;
280+ url ?: string ;
281+ final_url ?: string ;
282+ error_text ?: string ;
283+ }
284+ | undefined ;
269285 if ( args . url !== undefined ) {
270- await runBsk (
286+ // Combined start-with-URL: a lifecycle wait timeout (exit 4 +
287+ // reached:"timeout") must not cancel the session that was already
288+ // created — the page may still be usable. Genuine navigate failures
289+ // still fall through to starts.fail below.
290+ const navigateReply = await runBsk (
271291 deps ,
272292 exec ,
273293 [ "navigate" , "--session" , reply . session_id , args . url ] ,
274294 "navigate" ,
275295 reply . session_id ,
276296 undefined ,
277297 true ,
298+ { allowNavigationTimeout : true } ,
278299 ) ;
300+ if ( isNavigationLifecycleTimeoutPayload ( navigateReply ) ) {
301+ navigationTimeout = {
302+ reached : "timeout" ,
303+ url : typeof navigateReply . url === "string" ? navigateReply . url : undefined ,
304+ final_url :
305+ typeof navigateReply . final_url === "string" ? navigateReply . final_url : undefined ,
306+ error_text :
307+ typeof navigateReply . error_text === "string"
308+ ? navigateReply . error_text
309+ : undefined ,
310+ } ;
311+ deps . observation . setUrl (
312+ reply . session_id ,
313+ navigationTimeout . final_url ?? navigationTimeout . url ?? args . url ,
314+ ) ;
315+ } else {
316+ const okReply = navigateReply as {
317+ url ?: string ;
318+ final_url ?: string ;
319+ } ;
320+ deps . observation . setUrl (
321+ reply . session_id ,
322+ okReply . final_url ?? okReply . url ?? args . url ,
323+ ) ;
324+ }
279325 }
280326 await starts . claim ( record , exec . signal ) ;
327+ return {
328+ sessionId : reply . session_id ,
329+ browserInstanceId : reply . browser_instance_id ,
330+ ...( args . url !== undefined ? { url : args . url } : { } ) ,
331+ ...( args . device !== undefined ? { device : args . device } : { } ) ,
332+ ...( navigationTimeout !== undefined
333+ ? {
334+ reached : "timeout" as const ,
335+ warning :
336+ "Initial navigation timed out waiting for the page lifecycle; " +
337+ "the session remains open — observe the page and decide how to proceed." +
338+ ( navigationTimeout . error_text !== undefined
339+ ? ` (${ navigationTimeout . error_text } )`
340+ : "" ) ,
341+ }
342+ : { } ) ,
343+ } ;
281344 } catch ( error ) {
282345 await starts . fail ( record ) . catch ( ( ) => { } ) ;
283346 throw error ;
284347 }
285- return {
286- sessionId : reply . session_id ,
287- browserInstanceId : reply . browser_instance_id ,
288- ...( args . url !== undefined ? { url : args . url } : { } ) ,
289- ...( args . device !== undefined ? { device : args . device } : { } ) ,
290- } ;
291348 } ,
292349 presentCall : ( args ) => ( {
293350 card : "terminal" ,
0 commit comments