@@ -141,7 +141,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
141141 event . subscribe ( ( event , { workspace } ) => {
142142 switch ( event . type ) {
143143 case "server.instance.disposed" :
144- void bootstrap ( )
144+ void bootstrap ( { fatal : false } )
145145 break
146146 case "permission.replied" : {
147147 const requests = store . permission [ event . properties . sessionID ]
@@ -388,107 +388,128 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
388388 const exit = useExit ( )
389389 const args = useArgs ( )
390390
391+ // fatal:false callers (dispose events) queue a trailing run so agents are fetched
392+ // after plugin.init() completes, not during it.
393+ let bootstrapInFlight : Promise < void > | undefined
394+ let bootstrapQueued = false
395+
391396 async function bootstrap ( input : { fatal ?: boolean } = { } ) {
397+ if ( bootstrapInFlight ) {
398+ bootstrapQueued = true
399+ return bootstrapInFlight
400+ }
392401 const fatal = input . fatal ?? true
393- const workspace = project . workspace . current ( )
394- const projectPromise = project . sync ( )
395- const sessionListPromise = projectPromise . then ( ( ) => listSessions ( ) )
402+ const run = async ( ) => {
403+ const workspace = project . workspace . current ( )
404+ const projectPromise = project . sync ( )
405+ const sessionListPromise = projectPromise . then ( ( ) => listSessions ( ) )
396406
397- // blocking - include session.list when continuing a session
398- const providersPromise = sdk . client . config . providers ( { workspace } , { throwOnError : true } )
399- const providerListPromise = sdk . client . provider . list ( { workspace } , { throwOnError : true } )
400- const consoleStatePromise = sdk . client . experimental . console
401- . get ( { workspace } , { throwOnError : true } )
402- . then ( ( x ) => x . data )
403- . catch ( ( ) => emptyConsoleState )
404- const agentsPromise = sdk . client . app . agents ( { workspace } , { throwOnError : true } )
405- const configPromise = sdk . client . config . get ( { workspace } , { throwOnError : true } )
406- const blockingRequests : { name : string ; promise : Promise < unknown > } [ ] = [
407- { name : "config.providers" , promise : providersPromise } ,
408- { name : "provider.list" , promise : providerListPromise } ,
409- { name : "app.agents" , promise : agentsPromise } ,
410- { name : "config.get" , promise : configPromise } ,
411- { name : "project.sync" , promise : projectPromise } ,
412- ...( args . continue ? [ { name : "session.list" , promise : sessionListPromise } ] : [ ] ) ,
413- ]
407+ // blocking - include session.list when continuing a session
408+ const providersPromise = sdk . client . config . providers ( { workspace } , { throwOnError : true } )
409+ const providerListPromise = sdk . client . provider . list ( { workspace } , { throwOnError : true } )
410+ const consoleStatePromise = sdk . client . experimental . console
411+ . get ( { workspace } , { throwOnError : true } )
412+ . then ( ( x ) => x . data )
413+ . catch ( ( ) => emptyConsoleState )
414+ const agentsPromise = sdk . client . app . agents ( { workspace } , { throwOnError : true } )
415+ const configPromise = sdk . client . config . get ( { workspace } , { throwOnError : true } )
416+ const blockingRequests : { name : string ; promise : Promise < unknown > } [ ] = [
417+ { name : "config.providers" , promise : providersPromise } ,
418+ { name : "provider.list" , promise : providerListPromise } ,
419+ { name : "app.agents" , promise : agentsPromise } ,
420+ { name : "config.get" , promise : configPromise } ,
421+ { name : "project.sync" , promise : projectPromise } ,
422+ ...( args . continue ? [ { name : "session.list" , promise : sessionListPromise } ] : [ ] ) ,
423+ ]
414424
415- await Promise . allSettled ( blockingRequests . map ( ( r ) => r . promise ) )
416- . then ( ( settled ) => {
417- // Surface every failed endpoint in one labeled message instead of
418- // letting the first rejection drown its siblings as unhandled
419- // rejections.
420- const failure = aggregateFailures ( blockingRequests . map ( ( r , i ) => ( { name : r . name , result : settled [ i ] } ) ) )
421- if ( failure ) throw failure
422- } )
423- . then ( async ( ) => {
424- const providersResponse = providersPromise . then ( ( x ) => x . data ! )
425- const providerListResponse = providerListPromise . then ( ( x ) => x . data ! )
426- const consoleStateResponse = consoleStatePromise
427- const agentsResponse = agentsPromise . then ( ( x ) => x . data ?? [ ] )
428- const configResponse = configPromise . then ( ( x ) => x . data ! )
429- const sessionListResponse = args . continue ? sessionListPromise : undefined
425+ await Promise . allSettled ( blockingRequests . map ( ( r ) => r . promise ) )
426+ . then ( ( settled ) => {
427+ // Surface every failed endpoint in one labeled message instead of
428+ // letting the first rejection drown its siblings as unhandled
429+ // rejections.
430+ const failure = aggregateFailures ( blockingRequests . map ( ( r , i ) => ( { name : r . name , result : settled [ i ] } ) ) )
431+ if ( failure ) throw failure
432+ } )
433+ . then ( async ( ) => {
434+ const providersResponse = providersPromise . then ( ( x ) => x . data ! )
435+ const providerListResponse = providerListPromise . then ( ( x ) => x . data ! )
436+ const consoleStateResponse = consoleStatePromise
437+ const agentsResponse = agentsPromise . then ( ( x ) => x . data ?? [ ] )
438+ const configResponse = configPromise . then ( ( x ) => x . data ! )
439+ const sessionListResponse = args . continue ? sessionListPromise : undefined
430440
431- return Promise . all ( [
432- providersResponse ,
433- providerListResponse ,
434- consoleStateResponse ,
435- agentsResponse ,
436- configResponse ,
437- ...( sessionListResponse ? [ sessionListResponse ] : [ ] ) ,
438- ] ) . then ( ( responses ) => {
439- const providers = responses [ 0 ]
440- const providerList = responses [ 1 ]
441- const consoleState = responses [ 2 ]
442- const agents = responses [ 3 ]
443- const config = responses [ 4 ]
444- const sessions = responses [ 5 ]
441+ return Promise . all ( [
442+ providersResponse ,
443+ providerListResponse ,
444+ consoleStateResponse ,
445+ agentsResponse ,
446+ configResponse ,
447+ ...( sessionListResponse ? [ sessionListResponse ] : [ ] ) ,
448+ ] ) . then ( ( responses ) => {
449+ const providers = responses [ 0 ]
450+ const providerList = responses [ 1 ]
451+ const consoleState = responses [ 2 ]
452+ const agents = responses [ 3 ]
453+ const config = responses [ 4 ]
454+ const sessions = responses [ 5 ]
445455
446- batch ( ( ) => {
447- setStore ( "provider" , reconcile ( providers . providers ) )
448- setStore ( "provider_default" , reconcile ( providers . default ) )
449- setStore ( "provider_next" , reconcile ( providerList ) )
450- setStore ( "console_state" , reconcile ( consoleState ) )
451- setStore ( "agent" , reconcile ( agents ) )
452- setStore ( "config" , reconcile ( config ) )
453- if ( sessions !== undefined ) setStore ( "session" , reconcile ( sessions ) )
456+ batch ( ( ) => {
457+ setStore ( "provider" , reconcile ( providers . providers ) )
458+ setStore ( "provider_default" , reconcile ( providers . default ) )
459+ setStore ( "provider_next" , reconcile ( providerList ) )
460+ setStore ( "console_state" , reconcile ( consoleState ) )
461+ setStore ( "agent" , reconcile ( agents ) )
462+ setStore ( "config" , reconcile ( config ) )
463+ if ( sessions !== undefined ) setStore ( "session" , reconcile ( sessions ) )
464+ } )
454465 } )
455466 } )
456- } )
457- . then ( ( ) => {
458- if ( store . status !== "complete" ) setStore ( "status" , "partial" )
459- // non-blocking
460- void Promise . all ( [
461- ...( args . continue ? [ ] : [ sessionListPromise . then ( ( sessions ) => setStore ( "session" , reconcile ( sessions ) ) ) ] ) ,
462- consoleStatePromise . then ( ( consoleState ) => setStore ( "console_state" , reconcile ( consoleState ) ) ) ,
463- sdk . client . command . list ( { workspace } ) . then ( ( x ) => setStore ( "command" , reconcile ( x . data ?? [ ] ) ) ) ,
464- sdk . client . lsp . status ( { workspace } ) . then ( ( x ) => setStore ( "lsp" , reconcile ( x . data ?? [ ] ) ) ) ,
465- sdk . client . mcp . status ( { workspace } ) . then ( ( x ) => setStore ( "mcp" , reconcile ( x . data ?? { } ) ) ) ,
466- sdk . client . experimental . resource
467- . list ( { workspace } )
468- . then ( ( x ) => setStore ( "mcp_resource" , reconcile ( x . data ?? { } ) ) ) ,
469- sdk . client . formatter . status ( { workspace } ) . then ( ( x ) => setStore ( "formatter" , reconcile ( x . data ?? [ ] ) ) ) ,
470- sdk . client . session . status ( { workspace } ) . then ( ( x ) => {
471- setStore ( "session_status" , reconcile ( x . data ?? { } ) )
472- } ) ,
473- sdk . client . provider . auth ( { workspace } ) . then ( ( x ) => setStore ( "provider_auth" , reconcile ( x . data ?? { } ) ) ) ,
474- sdk . client . vcs . get ( { workspace } ) . then ( ( x ) => setStore ( "vcs" , reconcile ( x . data ) ) ) ,
475- project . workspace . sync ( ) ,
476- ] ) . then ( ( ) => {
477- setStore ( "status" , "complete" )
467+ . then ( ( ) => {
468+ if ( store . status !== "complete" ) setStore ( "status" , "partial" )
469+ // non-blocking
470+ void Promise . all ( [
471+ ...( args . continue
472+ ? [ ]
473+ : [ sessionListPromise . then ( ( sessions ) => setStore ( "session" , reconcile ( sessions ) ) ) ] ) ,
474+ consoleStatePromise . then ( ( consoleState ) => setStore ( "console_state" , reconcile ( consoleState ) ) ) ,
475+ sdk . client . command . list ( { workspace } ) . then ( ( x ) => setStore ( "command" , reconcile ( x . data ?? [ ] ) ) ) ,
476+ sdk . client . lsp . status ( { workspace } ) . then ( ( x ) => setStore ( "lsp" , reconcile ( x . data ?? [ ] ) ) ) ,
477+ sdk . client . mcp . status ( { workspace } ) . then ( ( x ) => setStore ( "mcp" , reconcile ( x . data ?? { } ) ) ) ,
478+ sdk . client . experimental . resource
479+ . list ( { workspace } )
480+ . then ( ( x ) => setStore ( "mcp_resource" , reconcile ( x . data ?? { } ) ) ) ,
481+ sdk . client . formatter . status ( { workspace } ) . then ( ( x ) => setStore ( "formatter" , reconcile ( x . data ?? [ ] ) ) ) ,
482+ sdk . client . session . status ( { workspace } ) . then ( ( x ) => {
483+ setStore ( "session_status" , reconcile ( x . data ?? { } ) )
484+ } ) ,
485+ sdk . client . provider . auth ( { workspace } ) . then ( ( x ) => setStore ( "provider_auth" , reconcile ( x . data ?? { } ) ) ) ,
486+ sdk . client . vcs . get ( { workspace } ) . then ( ( x ) => setStore ( "vcs" , reconcile ( x . data ) ) ) ,
487+ project . workspace . sync ( ) ,
488+ ] ) . then ( ( ) => {
489+ setStore ( "status" , "complete" )
490+ } )
478491 } )
479- } )
480- . catch ( async ( e ) => {
481- Log . Default . error ( "tui bootstrap failed" , {
482- error : e instanceof Error ? e . message : String ( e ) ,
483- name : e instanceof Error ? e . name : undefined ,
484- stack : e instanceof Error ? e . stack : undefined ,
492+ . catch ( async ( e ) => {
493+ Log . Default . error ( "tui bootstrap failed" , {
494+ error : e instanceof Error ? e . message : String ( e ) ,
495+ name : e instanceof Error ? e . name : undefined ,
496+ stack : e instanceof Error ? e . stack : undefined ,
497+ } )
498+ if ( fatal ) {
499+ await exit ( e )
500+ } else {
501+ throw e
502+ }
485503 } )
486- if ( fatal ) {
487- await exit ( e )
488- } else {
489- throw e
490- }
491- } )
504+ }
505+ bootstrapInFlight = run ( ) . finally ( ( ) => {
506+ bootstrapInFlight = undefined
507+ if ( bootstrapQueued ) {
508+ bootstrapQueued = false
509+ void bootstrap ( { fatal : false } )
510+ }
511+ } )
512+ return bootstrapInFlight
492513 }
493514
494515 onMount ( ( ) => {
0 commit comments