Skip to content

Commit 552094b

Browse files
committed
simplify mcp loading
1 parent acf3b00 commit 552094b

6 files changed

Lines changed: 69 additions & 139 deletions

File tree

packages/app/src/components/dialog-select-mcp.tsx

Lines changed: 7 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
import { useMutation } from "@tanstack/solid-query"
2-
import { Component, createEffect, createMemo, on, Show } from "solid-js"
3-
import { createStore } from "solid-js/store"
1+
import { useMutation, useQueryClient } from "@tanstack/solid-query"
2+
import { Component, createMemo, Show } from "solid-js"
43
import { useSync } from "@/context/sync"
54
import { useSDK } from "@/context/sdk"
65
import { Dialog } from "@opencode-ai/ui/dialog"
76
import { List } from "@opencode-ai/ui/list"
87
import { Switch } from "@opencode-ai/ui/switch"
9-
import { showToast } from "@opencode-ai/ui/toast"
108
import { useLanguage } from "@/context/language"
9+
import { loadMcpQuery } from "@/context/global-sync"
1110

1211
const statusLabels = {
1312
connected: "mcp.status.connected",
@@ -20,48 +19,7 @@ export const DialogSelectMcp: Component = () => {
2019
const sync = useSync()
2120
const sdk = useSDK()
2221
const language = useLanguage()
23-
const [state, setState] = createStore({
24-
done: false,
25-
loading: false,
26-
})
27-
28-
createEffect(
29-
on(
30-
() => sync.data.mcp_ready,
31-
(ready, prev) => {
32-
if (!ready && prev) setState("done", false)
33-
},
34-
{ defer: true },
35-
),
36-
)
37-
38-
createEffect(() => {
39-
if (state.done || state.loading) return
40-
if (sync.data.mcp_ready) {
41-
setState("done", true)
42-
return
43-
}
44-
45-
setState("loading", true)
46-
void sdk.client.mcp
47-
.status()
48-
.then((result) => {
49-
sync.set("mcp", result.data ?? {})
50-
// sync.set("mcp_ready", true)
51-
setState("done", true)
52-
})
53-
.catch((err) => {
54-
setState("done", true)
55-
showToast({
56-
variant: "error",
57-
title: language.t("common.requestFailed"),
58-
description: err instanceof Error ? err.message : String(err),
59-
})
60-
})
61-
.finally(() => {
62-
setState("loading", false)
63-
})
64-
})
22+
const queryClient = useQueryClient()
6523

6624
const items = createMemo(() =>
6725
Object.entries(sync.data.mcp ?? {})
@@ -71,16 +29,10 @@ export const DialogSelectMcp: Component = () => {
7129

7230
const toggle = useMutation(() => ({
7331
mutationFn: async (name: string) => {
74-
const status = sync.data.mcp[name]
75-
if (status?.status === "connected") {
76-
await sdk.client.mcp.disconnect({ name })
77-
} else {
78-
await sdk.client.mcp.connect({ name })
79-
}
80-
81-
const result = await sdk.client.mcp.status()
82-
if (result.data) sync.set("mcp", result.data)
32+
if (sync.data.mcp[name]?.status === "connected") await sdk.client.mcp.disconnect({ name })
33+
else await sdk.client.mcp.connect({ name })
8334
},
35+
onSuccess: () => queryClient.refetchQueries({ queryKey: loadMcpQuery(sync.directory).queryKey }),
8436
}))
8537

8638
const enabledCount = createMemo(() => items().filter((i) => i.status === "connected").length)

packages/app/src/components/status-popover-body.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useDialog } from "@opencode-ai/ui/context/dialog"
33
import { Icon } from "@opencode-ai/ui/icon"
44
import { Switch } from "@opencode-ai/ui/switch"
55
import { Tabs } from "@opencode-ai/ui/tabs"
6-
import { useMutation } from "@tanstack/solid-query"
6+
import { useMutation, useQueryClient } from "@tanstack/solid-query"
77
import { showToast } from "@opencode-ai/ui/toast"
88
import { useNavigate } from "@solidjs/router"
99
import { type Accessor, createEffect, createMemo, For, type JSXElement, onCleanup, Show } from "solid-js"
@@ -15,6 +15,7 @@ import { useSDK } from "@/context/sdk"
1515
import { normalizeServerUrl, ServerConnection, useServer } from "@/context/server"
1616
import { useSync } from "@/context/sync"
1717
import { useCheckServerHealth, type ServerHealth } from "@/utils/server-health"
18+
import { loadMcpQuery } from "@/context/global-sync"
1819

1920
const pollMs = 10_000
2021

@@ -137,14 +138,14 @@ const useMcpToggleMutation = () => {
137138
const sync = useSync()
138139
const sdk = useSDK()
139140
const language = useLanguage()
141+
const queryClient = useQueryClient()
140142

141143
return useMutation(() => ({
142144
mutationFn: async (name: string) => {
143145
const status = sync.data.mcp[name]
144146
await (status?.status === "connected" ? sdk.client.mcp.disconnect({ name }) : sdk.client.mcp.connect({ name }))
145-
const result = await sdk.client.mcp.status()
146-
if (result.data) sync.set("mcp", result.data)
147147
},
148+
onSuccess: () => queryClient.refetchQueries({ queryKey: loadMcpQuery(sync.directory).queryKey }),
148149
onError: (err) => {
149150
showToast({
150151
variant: "error",

packages/app/src/context/global-sync.tsx

Lines changed: 28 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@ import type {
1010
import { showToast } from "@opencode-ai/ui/toast"
1111
import { getFilename } from "@opencode-ai/shared/util/path"
1212
import { batch, createContext, getOwner, onCleanup, onMount, type ParentProps, untrack, useContext } from "solid-js"
13-
import { createStore, produce, reconcile, unwrap } from "solid-js/store"
13+
import { createStore, produce, reconcile } from "solid-js/store"
1414
import { useLanguage } from "@/context/language"
15-
import { Persist, persisted } from "@/utils/persist"
1615
import type { InitError } from "../pages/error"
1716
import { useGlobalSDK } from "./global-sdk"
1817
import {
@@ -31,9 +30,9 @@ import { estimateRootSessionTotal, loadRootSessionsWithFallback } from "./global
3130
import { trimSessions } from "./global-sync/session-trim"
3231
import type { ProjectMeta } from "./global-sync/types"
3332
import { SESSION_RECENT_LIMIT } from "./global-sync/types"
34-
import { sanitizeProject } from "./global-sync/utils"
3533
import { formatServerError } from "@/utils/server-errors"
3634
import { queryOptions, skipToken, useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/solid-query"
35+
import { createRefreshQueue } from "./global-sync/queue"
3736

3837
type GlobalStore = {
3938
ready: boolean
@@ -61,7 +60,7 @@ export const loadMcpQuery = (directory: string, sdk?: OpencodeClient) =>
6160
export const loadLspQuery = (directory: string, sdk?: OpencodeClient) =>
6261
queryOptions({
6362
queryKey: [directory, "lsp"],
64-
queryFn: sdk ? () => sdk.lsp.status().then((r) => r.data ?? {}) : skipToken,
63+
queryFn: sdk ? () => sdk.lsp.status().then((r) => r.data ?? []) : skipToken,
6564
})
6665

6766
function createGlobalSync() {
@@ -75,11 +74,6 @@ function createGlobalSync() {
7574
const sessionLoads = new Map<string, Promise<void>>()
7675
const sessionMeta = new Map<string, { limit: number }>()
7776

78-
const [projectCache, setProjectCache, projectInit] = persisted(
79-
Persist.global("globalSync.project", ["globalSync.project.v1"]),
80-
createStore({ value: [] as Project[] }),
81-
)
82-
8377
const [configQuery, providerQuery, pathQuery] = useQueries(() => ({
8478
queries: [loadGlobalConfigQuery(), loadProvidersQuery(null), loadPathQuery(null), loadProjectsQuery()],
8579
}))
@@ -88,7 +82,7 @@ function createGlobalSync() {
8882
get ready() {
8983
return bootstrap.isPending
9084
},
91-
project: projectCache.value,
85+
project: [],
9286
session_todo: {},
9387
provider_auth: {},
9488
get path() {
@@ -111,32 +105,18 @@ function createGlobalSync() {
111105
})
112106
const queryClient = useQueryClient()
113107

114-
let active = true
115-
let projectWritten = false
116108
let bootedAt = 0
117109
let bootingRoot = false
118110
let eventFrame: number | undefined
119111
let eventTimer: ReturnType<typeof setTimeout> | undefined
120112

121-
onCleanup(() => {
122-
active = false
123-
})
124113
onCleanup(() => {
125114
if (eventFrame !== undefined) cancelAnimationFrame(eventFrame)
126115
if (eventTimer !== undefined) clearTimeout(eventTimer)
127116
})
128117

129-
const cacheProjects = () => {
130-
setProjectCache(
131-
"value",
132-
untrack(() => globalStore.project.map(sanitizeProject)),
133-
)
134-
}
135-
136118
const setProjects = (next: Project[] | ((draft: Project[]) => Project[])) => {
137-
projectWritten = true
138119
setGlobalStore("project", next)
139-
cacheProjects()
140120
}
141121

142122
const setBootStore = ((...input: unknown[]) => {
@@ -171,16 +151,6 @@ function createGlobalSync() {
171151
return (setGlobalStore as (...args: unknown[]) => unknown)(...input)
172152
}) as typeof setGlobalStore
173153

174-
if (projectInit instanceof Promise) {
175-
void projectInit.then(() => {
176-
if (!active) return
177-
if (projectWritten) return
178-
const cached = projectCache.value
179-
if (cached.length === 0) return
180-
setGlobalStore("project", cached)
181-
})
182-
}
183-
184154
const setSessionTodo = (sessionID: string, todos: Todo[] | undefined) => {
185155
if (!sessionID) return
186156
if (!todos) {
@@ -197,11 +167,22 @@ function createGlobalSync() {
197167

198168
const paused = () => untrack(() => globalStore.reload) !== undefined
199169

200-
// const queue = createRefreshQueue({
201-
// paused,
202-
// bootstrap: () => queryClient.fetchQuery({ queryKey: ["bootstrap"] }),
203-
// bootstrapInstance,
204-
// })
170+
const queue = createRefreshQueue({
171+
paused,
172+
bootstrap: () => queryClient.fetchQuery({ queryKey: ["bootstrap"] }),
173+
bootstrapInstance,
174+
})
175+
176+
const sdkFor = (directory: string) => {
177+
const cached = sdkCache.get(directory)
178+
if (cached) return cached
179+
const sdk = globalSDK.createClient({
180+
directory,
181+
throwOnError: true,
182+
})
183+
sdkCache.set(directory, sdk)
184+
return sdk
185+
}
205186

206187
const children = createChildStoreManager({
207188
owner,
@@ -211,26 +192,16 @@ function createGlobalSync() {
211192
void bootstrapInstance(directory)
212193
},
213194
onDispose: (directory) => {
214-
// queue.clear(directory)
195+
queue.clear(directory)
215196
sessionMeta.delete(directory)
216197
sdkCache.delete(directory)
217198
clearProviderRev(directory)
218199
clearSessionPrefetchDirectory(directory)
219200
},
220201
translate: language.t,
202+
getSdk: sdkFor,
221203
})
222204

223-
const sdkFor = (directory: string) => {
224-
const cached = sdkCache.get(directory)
225-
if (cached) return cached
226-
const sdk = globalSDK.createClient({
227-
directory,
228-
throwOnError: true,
229-
})
230-
sdkCache.set(directory, sdk)
231-
return sdk
232-
}
233-
234205
async function loadSessions(directory: string) {
235206
const pending = sessionLoads.get(directory)
236207
if (pending) return pending
@@ -362,7 +333,7 @@ function createGlobalSync() {
362333
if (event.type === "server.connected" || event.type === "global.disposed") {
363334
if (recent) return
364335
for (const directory of Object.keys(children.children)) {
365-
// queue.push(directory)
336+
queue.push(directory)
366337
}
367338
}
368339
return
@@ -381,17 +352,15 @@ function createGlobalSync() {
381352
setSessionTodo,
382353
vcsCache: children.vcsCache.get(directory),
383354
loadLsp: () => {
384-
void queryClient.fetchQuery(loadLspQuery(directory, sdkFor(directory))).then((data) => {
385-
setStore("lsp", data ?? [])
386-
})
355+
void queryClient.fetchQuery(loadLspQuery(directory, sdkFor(directory)))
387356
},
388357
})
389358
})
390359

391360
onCleanup(unsub)
392-
// onCleanup(() => {
393-
// queue.dispose()
394-
// })
361+
onCleanup(() => {
362+
queue.dispose()
363+
})
395364
onCleanup(() => {
396365
for (const directory of Object.keys(children.children)) {
397366
children.disposeDirectory(directory)
@@ -427,7 +396,7 @@ function createGlobalSync() {
427396

428397
const updateConfigMutation = useMutation(() => ({
429398
mutationFn: (config: Config) => globalSDK.client.global.config.update({ config }),
430-
// onSuccess: () => bootstrap.refetch(),
399+
onSuccess: () => bootstrap.refetch(),
431400
}))
432401

433402
return {

packages/app/src/context/global-sync/bootstrap.ts

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -265,8 +265,6 @@ export async function bootstrapDirectory(input: {
265265
if (Object.keys(input.store.config).length === 0 && Object.keys(input.global.config).length > 0) {
266266
input.setStore("config", input.global.config)
267267
}
268-
input.setStore("mcp", {})
269-
input.setStore("lsp", [])
270268
if (loading) input.setStore("status", "partial")
271269

272270
const rev = (providerRev.get(input.directory) ?? 0) + 1
@@ -354,18 +352,14 @@ export async function bootstrapDirectory(input: {
354352
() => Promise.resolve(input.loadSessions(input.directory)),
355353
() => input.queryClient.fetchQuery(loadMcpQuery(input.directory, input.sdk)),
356354
() =>
357-
input.queryClient.ensureQueryData(
358-
loadProvidersQuery(input.directory, input.sdk),
359-
// .catch((err) => {
360-
// if (providerRev.get(input.directory) !== rev) console.error("Failed to refresh provider list", err)
361-
// const project = getFilename(input.directory)
362-
// showToast({
363-
// variant: "error",
364-
// title: input.translate("toast.project.reloadFailed.title", { project }),
365-
// description: formatServerError(err, input.translate),
366-
// })
367-
// })
368-
),
355+
input.queryClient.fetchQuery(loadProvidersQuery(input.directory, input.sdk)).catch((err) => {
356+
const project = getFilename(input.directory)
357+
showToast({
358+
variant: "error",
359+
title: input.translate("toast.project.reloadFailed.title", { project }),
360+
description: formatServerError(err, input.translate),
361+
})
362+
}),
369363
].filter(Boolean) as (() => Promise<any>)[]
370364

371365
await waitForPaint()

packages/app/src/context/global-sync/child-store.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ describe("createChildStoreManager", () => {
2222
onBootstrap() {},
2323
onDispose() {},
2424
translate: (key) => key,
25+
getSdk: () => null!,
2526
})
2627

2728
Array.from({ length: 30 }, (_, index) => `/pinned-${index}`).forEach((directory) => {

0 commit comments

Comments
 (0)