@@ -296,6 +296,11 @@ interface Props {
296296 providerModelsCache ?: ProviderModelsCache ;
297297 onProviderModelsCacheChange ?: Dispatch < SetStateAction < ProviderModelsCache > > ;
298298 agents : AgentInfo [ ] ;
299+ // True while the cold-start agent detection stream is still in flight
300+ // (`fetchAgentsStream` has not reached its terminal `done`). Onboarding
301+ // uses this to show the AMR cloud card in a detecting/skeleton state
302+ // instead of hiding it during the seconds AMR's probe takes to settle.
303+ agentsLoading ?: boolean ;
299304 daemonLive : boolean ;
300305 onModeChange : ( mode : ExecMode ) => void ;
301306 onAgentChange : ( id : string ) => void ;
@@ -415,6 +420,7 @@ export function EntryShell({
415420 providerModelsCache : sharedProviderModelsCache ,
416421 onProviderModelsCacheChange,
417422 agents,
423+ agentsLoading = false ,
418424 daemonLive,
419425 onModeChange,
420426 onAgentChange,
@@ -662,6 +668,7 @@ export function EntryShell({
662668 < OnboardingView
663669 config = { config }
664670 agents = { agents }
671+ agentsLoading = { agentsLoading }
665672 providerModelsCache = { activeProviderModelsCache }
666673 onProviderModelsCacheChange = { activeSetProviderModelsCache }
667674 daemonLive = { daemonLive }
@@ -903,6 +910,7 @@ function OnboardingView({
903910 providerModelsCache : sharedProviderModelsCache ,
904911 onProviderModelsCacheChange,
905912 agents,
913+ agentsLoading = false ,
906914 daemonLive,
907915 onModeChange,
908916 onAgentChange,
@@ -917,6 +925,7 @@ function OnboardingView({
917925 providerModelsCache ?: ProviderModelsCache ;
918926 onProviderModelsCacheChange ?: Dispatch < SetStateAction < ProviderModelsCache > > ;
919927 agents : AgentInfo [ ] ;
928+ agentsLoading ?: boolean ;
920929 daemonLive : boolean ;
921930 onModeChange : ( mode : ExecMode ) => void ;
922931 onAgentChange : ( id : string ) => void ;
@@ -937,6 +946,11 @@ function OnboardingView({
937946 const [ apiKeyVisible , setApiKeyVisible ] = useState ( false ) ;
938947 const [ cliScanStatus , setCliScanStatus ] = useState < 'idle' | 'scanning' | 'done' > ( 'idle' ) ;
939948 const [ amrStatus , setAmrStatus ] = useState < VelaLoginStatus | null > ( null ) ;
949+ // True while the one-shot AMR re-probe (fired when the cold-start stream
950+ // settled without surfacing AMR) is in flight. Combined with
951+ // `agentsLoading`, this is the full window during which AMR availability
952+ // is still undecided — and the AMR cloud card renders its skeleton.
953+ const [ amrRefreshPending , setAmrRefreshPending ] = useState ( false ) ;
940954 const [ amrLoginPending , setAmrLoginPending ] = useState ( false ) ;
941955 const [ amrLoginCancelPending , setAmrLoginCancelPending ] = useState ( false ) ;
942956 const [ newsletterSubmitting , setNewsletterSubmitting ] = useState ( false ) ;
@@ -1030,7 +1044,13 @@ function OnboardingView({
10301044 ( agent ) => agent . available && agent . id !== 'amr' && visibleAgentIds . includes ( agent . id ) ,
10311045 ) ;
10321046 const amrAgent = agents . find ( ( agent ) => agent . id === 'amr' && agent . available ) ?? null ;
1033- const showAmrCloudOption = amrAgent !== null || agents . length === 0 ;
1047+ // AMR availability is still undecided while the cold-start stream runs or
1048+ // the one-shot re-probe is in flight. During that window we show the AMR
1049+ // cloud card in a skeleton state rather than hiding it (the gap users hit
1050+ // today: card absent for several seconds, then it pops in). Once detection
1051+ // settles, the card is either real (amrAgent) or omitted.
1052+ const amrDetecting = ! amrAgent && ( agentsLoading || amrRefreshPending ) ;
1053+ const showAmrCloudOption = amrAgent !== null ;
10341054 const amrSignedIn = amrStatus ?. loggedIn === true ;
10351055 const amrSelectedAndSignedOut = runtime === 'amr' && ! amrSignedIn ;
10361056 const amrAgentChoice = config . agentModels ?. amr ?? { } ;
@@ -1070,10 +1090,16 @@ function OnboardingView({
10701090 } , [ amrAgent , onAgentChange , onModeChange , runtime ] ) ;
10711091
10721092 useEffect ( ( ) => {
1073- if ( amrAgent || amrAgentRefreshAttemptedRef . current ) return ;
1093+ // The cold-start stream finished without AMR. Re-probe once before we
1094+ // conclude AMR is unavailable, and keep the card in its skeleton state
1095+ // for the duration so it doesn't flash out-and-back-in.
1096+ if ( amrAgent || amrAgentRefreshAttemptedRef . current || agentsLoading ) return ;
10741097 amrAgentRefreshAttemptedRef . current = true ;
1075- void Promise . resolve ( onRefreshAgents ( ) ) . catch ( ( ) => undefined ) ;
1076- } , [ amrAgent , onRefreshAgents ] ) ;
1098+ setAmrRefreshPending ( true ) ;
1099+ void Promise . resolve ( onRefreshAgents ( ) )
1100+ . catch ( ( ) => undefined )
1101+ . finally ( ( ) => setAmrRefreshPending ( false ) ) ;
1102+ } , [ amrAgent , agentsLoading , onRefreshAgents ] ) ;
10771103
10781104 useEffect ( ( ) => {
10791105 if ( ! amrAgent ) return ;
@@ -1836,6 +1862,8 @@ function OnboardingView({
18361862 } }
18371863 />
18381864 </ div >
1865+ ) : amrDetecting ? (
1866+ < OnboardingAmrCloudSkeleton />
18391867 ) : null }
18401868 < div className = "onboarding-view__alternatives" >
18411869 { runtimeItems . map ( ( item ) => (
@@ -2777,6 +2805,53 @@ export function OnboardingDropdown(props: OnboardingDropdownProps) {
27772805 ) ;
27782806}
27792807
2808+ // Placeholder for the AMR cloud card shown while AMR availability is still
2809+ // being probed (the cold-start detection stream / one-shot re-probe). It
2810+ // mirrors the real card's footprint exactly — same featured/amr grid, same
2811+ // 246px min-height — so resolving to the real card causes no layout jump.
2812+ // The AMR brand (icon + name) is known up-front and rendered solid; only the
2813+ // version meta, benefit list, and model picker — the parts that depend on the
2814+ // probe result — shimmer. Non-interactive and announced via role="status".
2815+ function OnboardingAmrCloudSkeleton ( ) {
2816+ const t = useT ( ) ;
2817+ return (
2818+ < div className = "onboarding-view__amr-cloud-card" >
2819+ < div
2820+ className = "onboarding-view__card onboarding-view__card--featured onboarding-view__card--amr onboarding-view__card--benefit-aside onboarding-view__card--skeleton"
2821+ role = "status"
2822+ aria-busy = "true"
2823+ aria-label = { t ( 'common.loading' ) }
2824+ >
2825+ < span className = "onboarding-view__identity" >
2826+ < span className = "onboarding-view__icon onboarding-view__icon--asset" >
2827+ < AgentIcon id = "amr" size = { 52 } className = "onboarding-view__agent-logo" />
2828+ </ span >
2829+ < span className = "onboarding-view__card-copy" >
2830+ < span className = "onboarding-view__card-top" >
2831+ < strong > { t ( 'settings.amrCloud' ) } </ strong >
2832+ </ span >
2833+ < span className = "onboarding-view__skeleton-line onboarding-view__skeleton-line--meta" />
2834+ </ span >
2835+ </ span >
2836+ < span className = "onboarding-view__benefit-aside" aria-hidden = "true" >
2837+ < span className = "onboarding-view__benefit-stack onboarding-view__benefit-stack--skeleton" >
2838+ < span className = "onboarding-view__skeleton-line onboarding-view__skeleton-line--benefit" />
2839+ < span className = "onboarding-view__skeleton-line onboarding-view__skeleton-line--benefit" />
2840+ < span className = "onboarding-view__skeleton-line onboarding-view__skeleton-line--benefit" />
2841+ < span className = "onboarding-view__skeleton-line onboarding-view__skeleton-line--benefit" />
2842+ </ span >
2843+ </ span >
2844+ < span className = "onboarding-view__card-model" aria-hidden = "true" >
2845+ < span className = "onboarding-view__skeleton-model" >
2846+ < span className = "onboarding-view__skeleton-model-label" />
2847+ < span className = "onboarding-view__skeleton-model-bar" />
2848+ </ span >
2849+ </ span >
2850+ </ div >
2851+ </ div >
2852+ ) ;
2853+ }
2854+
27802855function OnboardingChoiceCard ( {
27812856 icon,
27822857 agentIconId,
0 commit comments