diff options
| author | Adam <[email protected]> | 2026-01-20 10:00:59 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2026-01-20 10:01:04 -0600 |
| commit | 8595dae1a47bff819bb507d77d0dddf14c335757 (patch) | |
| tree | f8fa7a333d4e792c95f0b2fed11350e60b75f41b /packages/app/src/context | |
| parent | c365f0a7c12ccd9e2dc85108934092532499b03c (diff) | |
| download | opencode-8595dae1a47bff819bb507d77d0dddf14c335757.tar.gz opencode-8595dae1a47bff819bb507d77d0dddf14c335757.zip | |
fix(app): session loading loop
Diffstat (limited to 'packages/app/src/context')
| -rw-r--r-- | packages/app/src/context/global-sync.tsx | 220 |
1 files changed, 127 insertions, 93 deletions
diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx index 69a2b8ce2..6a2102640 100644 --- a/packages/app/src/context/global-sync.tsx +++ b/packages/app/src/context/global-sync.tsx @@ -88,6 +88,10 @@ type VcsCache = { ready: Accessor<boolean> } +type ChildOptions = { + bootstrap?: boolean +} + function createGlobalSync() { const globalSDK = useGlobalSDK() const platform = usePlatform() @@ -127,8 +131,10 @@ function createGlobalSync() { }) const children: Record<string, [Store<State>, SetStoreFunction<State>]> = {} + const booting = new Map<string, Promise<void>>() + const sessionLoads = new Map<string, Promise<void>>() - function child(directory: string) { + function ensureChild(directory: string) { if (!directory) console.error("No directory provided") if (!children[directory]) { const cache = runWithOwner(owner, () => @@ -163,7 +169,6 @@ function createGlobalSync() { message: {}, part: {}, }) - bootstrapInstance(directory) } runWithOwner(owner, init) @@ -173,11 +178,23 @@ function createGlobalSync() { return childStore } + function child(directory: string, options: ChildOptions = {}) { + const childStore = ensureChild(directory) + const shouldBootstrap = options.bootstrap ?? true + if (shouldBootstrap && childStore[0].status === "loading") { + void bootstrapInstance(directory) + } + return childStore + } + async function loadSessions(directory: string) { - const [store, setStore] = child(directory) + const pending = sessionLoads.get(directory) + if (pending) return pending + + const [store, setStore] = child(directory, { bootstrap: false }) const limit = store.limit - return globalSDK.client.session + const promise = globalSDK.client.session .list({ directory, roots: true }) .then((x) => { const nonArchived = (x.data ?? []) @@ -208,13 +225,23 @@ function createGlobalSync() { const project = getFilename(directory) showToast({ title: `Failed to load sessions for ${project}`, description: err.message }) }) + + sessionLoads.set(directory, promise) + promise.finally(() => { + sessionLoads.delete(directory) + }) + return promise } async function bootstrapInstance(directory: string) { if (!directory) return - const [store, setStore] = child(directory) - const cache = vcsCache.get(directory) - if (!cache) return + const pending = booting.get(directory) + if (pending) return pending + + const promise = (async () => { + const [store, setStore] = ensureChild(directory) + const cache = vcsCache.get(directory) + if (!cache) return const sdk = createOpencodeClient({ baseUrl: globalSDK.url, fetch: platform.fetch, @@ -250,98 +277,105 @@ function createGlobalSync() { config: () => sdk.config.get().then((x) => setStore("config", x.data!)), } - try { - await Promise.all(Object.values(blockingRequests).map((p) => retry(p))) - } catch (err) { - console.error("Failed to bootstrap instance", err) - const project = getFilename(directory) - const message = err instanceof Error ? err.message : String(err) - showToast({ title: `Failed to reload ${project}`, description: message }) - setStore("status", "partial") - return - } + try { + await Promise.all(Object.values(blockingRequests).map((p) => retry(p))) + } catch (err) { + console.error("Failed to bootstrap instance", err) + const project = getFilename(directory) + const message = err instanceof Error ? err.message : String(err) + showToast({ title: `Failed to reload ${project}`, description: message }) + setStore("status", "partial") + return + } - if (store.status !== "complete") setStore("status", "partial") - - Promise.all([ - sdk.path.get().then((x) => setStore("path", x.data!)), - sdk.command.list().then((x) => setStore("command", x.data ?? [])), - sdk.session.status().then((x) => setStore("session_status", x.data!)), - loadSessions(directory), - sdk.mcp.status().then((x) => setStore("mcp", x.data!)), - sdk.lsp.status().then((x) => setStore("lsp", x.data!)), - sdk.vcs.get().then((x) => { - const next = x.data ?? store.vcs - setStore("vcs", next) - if (next?.branch) cache.setStore("value", next) - }), - sdk.permission.list().then((x) => { - const grouped: Record<string, PermissionRequest[]> = {} - for (const perm of x.data ?? []) { - if (!perm?.id || !perm.sessionID) continue - const existing = grouped[perm.sessionID] - if (existing) { - existing.push(perm) - continue + if (store.status !== "complete") setStore("status", "partial") + + Promise.all([ + sdk.path.get().then((x) => setStore("path", x.data!)), + sdk.command.list().then((x) => setStore("command", x.data ?? [])), + sdk.session.status().then((x) => setStore("session_status", x.data!)), + loadSessions(directory), + sdk.mcp.status().then((x) => setStore("mcp", x.data!)), + sdk.lsp.status().then((x) => setStore("lsp", x.data!)), + sdk.vcs.get().then((x) => { + const next = x.data ?? store.vcs + setStore("vcs", next) + if (next?.branch) cache.setStore("value", next) + }), + sdk.permission.list().then((x) => { + const grouped: Record<string, PermissionRequest[]> = {} + for (const perm of x.data ?? []) { + if (!perm?.id || !perm.sessionID) continue + const existing = grouped[perm.sessionID] + if (existing) { + existing.push(perm) + continue + } + grouped[perm.sessionID] = [perm] } - grouped[perm.sessionID] = [perm] - } - batch(() => { - for (const sessionID of Object.keys(store.permission)) { - if (grouped[sessionID]) continue - setStore("permission", sessionID, []) - } - for (const [sessionID, permissions] of Object.entries(grouped)) { - setStore( - "permission", - sessionID, - reconcile( - permissions - .filter((p) => !!p?.id) - .slice() - .sort((a, b) => a.id.localeCompare(b.id)), - { key: "id" }, - ), - ) - } - }) - }), - sdk.question.list().then((x) => { - const grouped: Record<string, QuestionRequest[]> = {} - for (const question of x.data ?? []) { - if (!question?.id || !question.sessionID) continue - const existing = grouped[question.sessionID] - if (existing) { - existing.push(question) - continue + batch(() => { + for (const sessionID of Object.keys(store.permission)) { + if (grouped[sessionID]) continue + setStore("permission", sessionID, []) + } + for (const [sessionID, permissions] of Object.entries(grouped)) { + setStore( + "permission", + sessionID, + reconcile( + permissions + .filter((p) => !!p?.id) + .slice() + .sort((a, b) => a.id.localeCompare(b.id)), + { key: "id" }, + ), + ) + } + }) + }), + sdk.question.list().then((x) => { + const grouped: Record<string, QuestionRequest[]> = {} + for (const question of x.data ?? []) { + if (!question?.id || !question.sessionID) continue + const existing = grouped[question.sessionID] + if (existing) { + existing.push(question) + continue + } + grouped[question.sessionID] = [question] } - grouped[question.sessionID] = [question] - } - batch(() => { - for (const sessionID of Object.keys(store.question)) { - if (grouped[sessionID]) continue - setStore("question", sessionID, []) - } - for (const [sessionID, questions] of Object.entries(grouped)) { - setStore( - "question", - sessionID, - reconcile( - questions - .filter((q) => !!q?.id) - .slice() - .sort((a, b) => a.id.localeCompare(b.id)), - { key: "id" }, - ), - ) - } - }) - }), - ]).then(() => { - setStore("status", "complete") + batch(() => { + for (const sessionID of Object.keys(store.question)) { + if (grouped[sessionID]) continue + setStore("question", sessionID, []) + } + for (const [sessionID, questions] of Object.entries(grouped)) { + setStore( + "question", + sessionID, + reconcile( + questions + .filter((q) => !!q?.id) + .slice() + .sort((a, b) => a.id.localeCompare(b.id)), + { key: "id" }, + ), + ) + } + }) + }), + ]).then(() => { + setStore("status", "complete") + }) + })() + + booting.set(directory, promise) + promise.finally(() => { + booting.delete(directory) }) + return promise } const unsub = globalSDK.event.listen((e) => { |
