summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/context
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-01-20 10:00:59 -0600
committerAdam <[email protected]>2026-01-20 10:01:04 -0600
commit8595dae1a47bff819bb507d77d0dddf14c335757 (patch)
treef8fa7a333d4e792c95f0b2fed11350e60b75f41b /packages/app/src/context
parentc365f0a7c12ccd9e2dc85108934092532499b03c (diff)
downloadopencode-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.tsx220
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) => {