@@ -31,52 +31,102 @@ type GlobalStore = {
3131 reload : undefined | "pending" | "complete"
3232}
3333
34+ function waitForPaint ( ) {
35+ return new Promise < void > ( ( resolve ) => {
36+ let done = false
37+ const finish = ( ) => {
38+ if ( done ) return
39+ done = true
40+ resolve ( )
41+ }
42+ const timer = setTimeout ( finish , 50 )
43+ if ( typeof requestAnimationFrame !== "function" ) return
44+ requestAnimationFrame ( ( ) => {
45+ clearTimeout ( timer )
46+ finish ( )
47+ } )
48+ } )
49+ }
50+
51+ function errors ( list : PromiseSettledResult < unknown > [ ] ) {
52+ return list . filter ( ( item ) : item is PromiseRejectedResult => item . status === "rejected" ) . map ( ( item ) => item . reason )
53+ }
54+
55+ function runAll ( list : Array < ( ) => Promise < unknown > > ) {
56+ return Promise . allSettled ( list . map ( ( item ) => item ( ) ) )
57+ }
58+
59+ function showErrors ( input : {
60+ errors : unknown [ ]
61+ title : string
62+ translate : ( key : string , vars ?: Record < string , string | number > ) => string
63+ formatMoreCount : ( count : number ) => string
64+ } ) {
65+ if ( input . errors . length === 0 ) return
66+ const message = formatServerError ( input . errors [ 0 ] , input . translate )
67+ const more = input . errors . length > 1 ? input . formatMoreCount ( input . errors . length - 1 ) : ""
68+ showToast ( {
69+ variant : "error" ,
70+ title : input . title ,
71+ description : message + more ,
72+ } )
73+ }
74+
3475export async function bootstrapGlobal ( input : {
3576 globalSDK : OpencodeClient
3677 requestFailedTitle : string
3778 translate : ( key : string , vars ?: Record < string , string | number > ) => string
3879 formatMoreCount : ( count : number ) => string
3980 setGlobalStore : SetStoreFunction < GlobalStore >
4081} ) {
41- const tasks = [
42- retry ( ( ) =>
43- input . globalSDK . path . get ( ) . then ( ( x ) => {
44- input . setGlobalStore ( "path" , x . data ! )
45- } ) ,
46- ) ,
47- retry ( ( ) =>
48- input . globalSDK . global . config . get ( ) . then ( ( x ) => {
49- input . setGlobalStore ( "config" , x . data ! )
50- } ) ,
51- ) ,
52- retry ( ( ) =>
53- input . globalSDK . project . list ( ) . then ( ( x ) => {
54- const projects = ( x . data ?? [ ] )
55- . filter ( ( p ) => ! ! p ?. id )
56- . filter ( ( p ) => ! ! p . worktree && ! p . worktree . includes ( "opencode-test" ) )
57- . slice ( )
58- . sort ( ( a , b ) => cmp ( a . id , b . id ) )
59- input . setGlobalStore ( "project" , projects )
60- } ) ,
61- ) ,
62- retry ( ( ) =>
63- input . globalSDK . provider . list ( ) . then ( ( x ) => {
64- input . setGlobalStore ( "provider" , normalizeProviderList ( x . data ! ) )
65- } ) ,
66- ) ,
82+ const fast = [
83+ ( ) =>
84+ retry ( ( ) =>
85+ input . globalSDK . path . get ( ) . then ( ( x ) => {
86+ input . setGlobalStore ( "path" , x . data ! )
87+ } ) ,
88+ ) ,
89+ ( ) =>
90+ retry ( ( ) =>
91+ input . globalSDK . global . config . get ( ) . then ( ( x ) => {
92+ input . setGlobalStore ( "config" , x . data ! )
93+ } ) ,
94+ ) ,
95+ ( ) =>
96+ retry ( ( ) =>
97+ input . globalSDK . provider . list ( ) . then ( ( x ) => {
98+ input . setGlobalStore ( "provider" , normalizeProviderList ( x . data ! ) )
99+ } ) ,
100+ ) ,
67101 ]
68102
69- const results = await Promise . allSettled ( tasks )
70- const errors = results . filter ( ( r ) : r is PromiseRejectedResult => r . status === "rejected" ) . map ( ( r ) => r . reason )
71- if ( errors . length ) {
72- const message = formatServerError ( errors [ 0 ] , input . translate )
73- const more = errors . length > 1 ? input . formatMoreCount ( errors . length - 1 ) : ""
74- showToast ( {
75- variant : "error" ,
76- title : input . requestFailedTitle ,
77- description : message + more ,
78- } )
79- }
103+ const slow = [
104+ ( ) =>
105+ retry ( ( ) =>
106+ input . globalSDK . project . list ( ) . then ( ( x ) => {
107+ const projects = ( x . data ?? [ ] )
108+ . filter ( ( p ) => ! ! p ?. id )
109+ . filter ( ( p ) => ! ! p . worktree && ! p . worktree . includes ( "opencode-test" ) )
110+ . slice ( )
111+ . sort ( ( a , b ) => cmp ( a . id , b . id ) )
112+ input . setGlobalStore ( "project" , projects )
113+ } ) ,
114+ ) ,
115+ ]
116+
117+ showErrors ( {
118+ errors : errors ( await runAll ( fast ) ) ,
119+ title : input . requestFailedTitle ,
120+ translate : input . translate ,
121+ formatMoreCount : input . formatMoreCount ,
122+ } )
123+ await waitForPaint ( )
124+ showErrors ( {
125+ errors : errors ( await runAll ( slow ) ) ,
126+ title : input . requestFailedTitle ,
127+ translate : input . translate ,
128+ formatMoreCount : input . formatMoreCount ,
129+ } )
80130 input . setGlobalStore ( "ready" , true )
81131}
82132
@@ -119,95 +169,113 @@ export async function bootstrapDirectory(input: {
119169 }
120170 if ( loading ) input . setStore ( "status" , "partial" )
121171
122- const results = await Promise . allSettled ( [
123- seededProject
124- ? Promise . resolve ( )
125- : retry ( ( ) => input . sdk . project . current ( ) ) . then ( ( x ) => input . setStore ( "project" , x . data ! . id ) ) ,
126- retry ( ( ) =>
127- input . sdk . provider . list ( ) . then ( ( x ) => {
128- input . setStore ( "provider" , normalizeProviderList ( x . data ! ) )
129- } ) ,
130- ) ,
131- retry ( ( ) => input . sdk . app . agents ( ) . then ( ( x ) => input . setStore ( "agent" , x . data ?? [ ] ) ) ) ,
132- retry ( ( ) => input . sdk . config . get ( ) . then ( ( x ) => input . setStore ( "config" , x . data ! ) ) ) ,
133- retry ( ( ) =>
134- input . sdk . path . get ( ) . then ( ( x ) => {
135- input . setStore ( "path" , x . data ! )
136- const next = projectID ( x . data ?. directory ?? input . directory , input . global . project )
137- if ( next ) input . setStore ( "project" , next )
138- } ) ,
139- ) ,
140- retry ( ( ) => input . sdk . command . list ( ) . then ( ( x ) => input . setStore ( "command" , x . data ?? [ ] ) ) ) ,
141- retry ( ( ) => input . sdk . session . status ( ) . then ( ( x ) => input . setStore ( "session_status" , x . data ! ) ) ) ,
142- input . loadSessions ( input . directory ) ,
143- retry ( ( ) => input . sdk . mcp . status ( ) . then ( ( x ) => input . setStore ( "mcp" , x . data ! ) ) ) ,
144- retry ( ( ) => input . sdk . lsp . status ( ) . then ( ( x ) => input . setStore ( "lsp" , x . data ! ) ) ) ,
145- retry ( ( ) =>
146- input . sdk . vcs . get ( ) . then ( ( x ) => {
147- const next = x . data ?? input . store . vcs
148- input . setStore ( "vcs" , next )
149- if ( next ?. branch ) input . vcsCache . setStore ( "value" , next )
150- } ) ,
151- ) ,
152- retry ( ( ) =>
153- input . sdk . permission . list ( ) . then ( ( x ) => {
154- const grouped = groupBySession (
155- ( x . data ?? [ ] ) . filter ( ( perm ) : perm is PermissionRequest => ! ! perm ?. id && ! ! perm . sessionID ) ,
156- )
157- batch ( ( ) => {
158- for ( const sessionID of Object . keys ( input . store . permission ) ) {
159- if ( grouped [ sessionID ] ) continue
160- input . setStore ( "permission" , sessionID , [ ] )
161- }
162- for ( const [ sessionID , permissions ] of Object . entries ( grouped ) ) {
163- input . setStore (
164- "permission" ,
165- sessionID ,
166- reconcile (
167- permissions . filter ( ( p ) => ! ! p ?. id ) . sort ( ( a , b ) => cmp ( a . id , b . id ) ) ,
168- { key : "id" } ,
169- ) ,
170- )
171- }
172- } )
173- } ) ,
174- ) ,
175- retry ( ( ) =>
176- input . sdk . question . list ( ) . then ( ( x ) => {
177- const grouped = groupBySession ( ( x . data ?? [ ] ) . filter ( ( q ) : q is QuestionRequest => ! ! q ?. id && ! ! q . sessionID ) )
178- batch ( ( ) => {
179- for ( const sessionID of Object . keys ( input . store . question ) ) {
180- if ( grouped [ sessionID ] ) continue
181- input . setStore ( "question" , sessionID , [ ] )
182- }
183- for ( const [ sessionID , questions ] of Object . entries ( grouped ) ) {
184- input . setStore (
185- "question" ,
186- sessionID ,
187- reconcile (
188- questions . filter ( ( q ) => ! ! q ?. id ) . sort ( ( a , b ) => cmp ( a . id , b . id ) ) ,
189- { key : "id" } ,
190- ) ,
191- )
192- }
193- } )
194- } ) ,
195- ) ,
196- ] )
197-
198- const errors = results
199- . filter ( ( item ) : item is PromiseRejectedResult => item . status === "rejected" )
200- . map ( ( item ) => item . reason )
201- if ( errors . length > 0 ) {
202- console . error ( "Failed to bootstrap instance" , errors [ 0 ] )
172+ const fast = [
173+ ( ) =>
174+ seededProject
175+ ? Promise . resolve ( )
176+ : retry ( ( ) => input . sdk . project . current ( ) ) . then ( ( x ) => input . setStore ( "project" , x . data ! . id ) ) ,
177+ ( ) => retry ( ( ) => input . sdk . app . agents ( ) . then ( ( x ) => input . setStore ( "agent" , x . data ?? [ ] ) ) ) ,
178+ ( ) => retry ( ( ) => input . sdk . config . get ( ) . then ( ( x ) => input . setStore ( "config" , x . data ! ) ) ) ,
179+ ( ) =>
180+ retry ( ( ) =>
181+ input . sdk . path . get ( ) . then ( ( x ) => {
182+ input . setStore ( "path" , x . data ! )
183+ const next = projectID ( x . data ?. directory ?? input . directory , input . global . project )
184+ if ( next ) input . setStore ( "project" , next )
185+ } ) ,
186+ ) ,
187+ ( ) => retry ( ( ) => input . sdk . session . status ( ) . then ( ( x ) => input . setStore ( "session_status" , x . data ! ) ) ) ,
188+ ( ) =>
189+ retry ( ( ) =>
190+ input . sdk . vcs . get ( ) . then ( ( x ) => {
191+ const next = x . data ?? input . store . vcs
192+ input . setStore ( "vcs" , next )
193+ if ( next ?. branch ) input . vcsCache . setStore ( "value" , next )
194+ } ) ,
195+ ) ,
196+ ( ) => retry ( ( ) => input . sdk . command . list ( ) . then ( ( x ) => input . setStore ( "command" , x . data ?? [ ] ) ) ) ,
197+ ( ) =>
198+ retry ( ( ) =>
199+ input . sdk . permission . list ( ) . then ( ( x ) => {
200+ const grouped = groupBySession (
201+ ( x . data ?? [ ] ) . filter ( ( perm ) : perm is PermissionRequest => ! ! perm ?. id && ! ! perm . sessionID ) ,
202+ )
203+ batch ( ( ) => {
204+ for ( const sessionID of Object . keys ( input . store . permission ) ) {
205+ if ( grouped [ sessionID ] ) continue
206+ input . setStore ( "permission" , sessionID , [ ] )
207+ }
208+ for ( const [ sessionID , permissions ] of Object . entries ( grouped ) ) {
209+ input . setStore (
210+ "permission" ,
211+ sessionID ,
212+ reconcile (
213+ permissions . filter ( ( p ) => ! ! p ?. id ) . sort ( ( a , b ) => cmp ( a . id , b . id ) ) ,
214+ { key : "id" } ,
215+ ) ,
216+ )
217+ }
218+ } )
219+ } ) ,
220+ ) ,
221+ ( ) =>
222+ retry ( ( ) =>
223+ input . sdk . question . list ( ) . then ( ( x ) => {
224+ const grouped = groupBySession ( ( x . data ?? [ ] ) . filter ( ( q ) : q is QuestionRequest => ! ! q ?. id && ! ! q . sessionID ) )
225+ batch ( ( ) => {
226+ for ( const sessionID of Object . keys ( input . store . question ) ) {
227+ if ( grouped [ sessionID ] ) continue
228+ input . setStore ( "question" , sessionID , [ ] )
229+ }
230+ for ( const [ sessionID , questions ] of Object . entries ( grouped ) ) {
231+ input . setStore (
232+ "question" ,
233+ sessionID ,
234+ reconcile (
235+ questions . filter ( ( q ) => ! ! q ?. id ) . sort ( ( a , b ) => cmp ( a . id , b . id ) ) ,
236+ { key : "id" } ,
237+ ) ,
238+ )
239+ }
240+ } )
241+ } ) ,
242+ ) ,
243+ ]
244+
245+ const slow = [
246+ ( ) =>
247+ retry ( ( ) =>
248+ input . sdk . provider . list ( ) . then ( ( x ) => {
249+ input . setStore ( "provider" , normalizeProviderList ( x . data ! ) )
250+ } ) ,
251+ ) ,
252+ ( ) => Promise . resolve ( input . loadSessions ( input . directory ) ) ,
253+ ( ) => retry ( ( ) => input . sdk . mcp . status ( ) . then ( ( x ) => input . setStore ( "mcp" , x . data ! ) ) ) ,
254+ ( ) => retry ( ( ) => input . sdk . lsp . status ( ) . then ( ( x ) => input . setStore ( "lsp" , x . data ! ) ) ) ,
255+ ]
256+
257+ const errs = errors ( await runAll ( fast ) )
258+ if ( errs . length > 0 ) {
259+ console . error ( "Failed to bootstrap instance" , errs [ 0 ] )
260+ const project = getFilename ( input . directory )
261+ showToast ( {
262+ variant : "error" ,
263+ title : input . translate ( "toast.project.reloadFailed.title" , { project } ) ,
264+ description : formatServerError ( errs [ 0 ] , input . translate ) ,
265+ } )
266+ }
267+
268+ await waitForPaint ( )
269+ const slowErrs = errors ( await runAll ( slow ) )
270+ if ( slowErrs . length > 0 ) {
271+ console . error ( "Failed to finish bootstrap instance" , slowErrs [ 0 ] )
203272 const project = getFilename ( input . directory )
204273 showToast ( {
205274 variant : "error" ,
206275 title : input . translate ( "toast.project.reloadFailed.title" , { project } ) ,
207- description : formatServerError ( errors [ 0 ] , input . translate ) ,
276+ description : formatServerError ( slowErrs [ 0 ] , input . translate ) ,
208277 } )
209- return
210278 }
211279
212- if ( loading ) input . setStore ( "status" , "complete" )
280+ if ( loading && errs . length === 0 && slowErrs . length === 0 ) input . setStore ( "status" , "complete" )
213281}
0 commit comments