summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/context
diff options
context:
space:
mode:
Diffstat (limited to 'packages/app/src/context')
-rw-r--r--packages/app/src/context/file.tsx2
-rw-r--r--packages/app/src/context/global-sdk.tsx8
-rw-r--r--packages/app/src/context/global-sync.tsx103
-rw-r--r--packages/app/src/context/global-sync/bootstrap.ts316
-rw-r--r--packages/app/src/context/global-sync/child-store.ts5
-rw-r--r--packages/app/src/context/global-sync/event-reducer.ts2
-rw-r--r--packages/app/src/context/global-sync/queue.ts1
-rw-r--r--packages/app/src/context/global-sync/types.ts2
-rw-r--r--packages/app/src/context/layout.tsx6
-rw-r--r--packages/app/src/context/local.tsx52
-rw-r--r--packages/app/src/context/notification.tsx4
-rw-r--r--packages/app/src/context/permission-auto-respond.test.ts2
-rw-r--r--packages/app/src/context/permission-auto-respond.ts2
-rw-r--r--packages/app/src/context/prompt.tsx2
-rw-r--r--packages/app/src/context/sync.tsx4
-rw-r--r--packages/app/src/context/terminal.tsx4
16 files changed, 251 insertions, 264 deletions
diff --git a/packages/app/src/context/file.tsx b/packages/app/src/context/file.tsx
index f8fec7142..8998731a6 100644
--- a/packages/app/src/context/file.tsx
+++ b/packages/app/src/context/file.tsx
@@ -3,7 +3,7 @@ import { createStore, produce, reconcile } from "solid-js/store"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { showToast } from "@opencode-ai/ui/toast"
import { useParams } from "@solidjs/router"
-import { getFilename } from "@opencode-ai/util/path"
+import { getFilename } from "@opencode-ai/shared/util/path"
import { useSDK } from "./sdk"
import { useSync } from "./sync"
import { useLanguage } from "@/context/language"
diff --git a/packages/app/src/context/global-sdk.tsx b/packages/app/src/context/global-sdk.tsx
index 1205a8fa8..e53d60d5a 100644
--- a/packages/app/src/context/global-sdk.tsx
+++ b/packages/app/src/context/global-sdk.tsx
@@ -128,6 +128,7 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
if (started) return run
started = true
run = (async () => {
+ // oxlint-disable-next-line no-unmodified-loop-condition -- `started` is set to false by stop() which also aborts; both flags are checked to allow graceful exit
while (!abort.signal.aborted && started) {
attempt = new AbortController()
lastEventAt = Date.now()
@@ -155,7 +156,12 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
resetHeartbeat()
streamErrorLogged = false
const directory = event.directory ?? "global"
- const payload = event.payload
+ if (event.payload.type === "sync") {
+ continue
+ }
+
+ const payload = event.payload as Event
+
const k = key(directory, payload)
if (k) {
const i = coalesced.get(k)
diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx
index 0cf3570a8..6ff60f161 100644
--- a/packages/app/src/context/global-sync.tsx
+++ b/packages/app/src/context/global-sync.tsx
@@ -8,7 +8,7 @@ import type {
Todo,
} from "@opencode-ai/sdk/v2/client"
import { showToast } from "@opencode-ai/ui/toast"
-import { getFilename } from "@opencode-ai/util/path"
+import { getFilename } from "@opencode-ai/shared/util/path"
import { createContext, getOwner, onCleanup, onMount, type ParentProps, untrack, useContext } from "solid-js"
import { createStore, produce, reconcile } from "solid-js/store"
import { useLanguage } from "@/context/language"
@@ -26,6 +26,7 @@ import type { ProjectMeta } from "./global-sync/types"
import { SESSION_RECENT_LIMIT } from "./global-sync/types"
import { sanitizeProject } from "./global-sync/utils"
import { formatServerError } from "@/utils/server-errors"
+import { queryOptions, skipToken, useQueryClient } from "@tanstack/solid-query"
type GlobalStore = {
ready: boolean
@@ -41,6 +42,9 @@ type GlobalStore = {
reload: undefined | "pending" | "complete"
}
+export const loadSessionsQuery = (directory: string) =>
+ queryOptions<null>({ queryKey: [directory, "loadSessions"], queryFn: skipToken })
+
function createGlobalSync() {
const globalSDK = useGlobalSDK()
const language = useLanguage()
@@ -67,6 +71,7 @@ function createGlobalSync() {
config: {},
reload: undefined,
})
+ const queryClient = useQueryClient()
let active = true
let projectWritten = false
@@ -198,46 +203,53 @@ function createGlobalSync() {
}
const limit = Math.max(store.limit + SESSION_RECENT_LIMIT, SESSION_RECENT_LIMIT)
- const promise = loadRootSessionsWithFallback({
- directory,
- limit,
- list: (query) => globalSDK.client.session.list(query),
- })
- .then((x) => {
- const nonArchived = (x.data ?? [])
- .filter((s) => !!s?.id)
- .filter((s) => !s.time?.archived)
- .sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))
- const limit = store.limit
- const childSessions = store.session.filter((s) => !!s.parentID)
- const sessions = trimSessions([...nonArchived, ...childSessions], {
- limit,
- permission: store.permission,
- })
- setStore(
- "sessionTotal",
- estimateRootSessionTotal({
- count: nonArchived.length,
- limit: x.limit,
- limited: x.limited,
- }),
- )
- setStore("session", reconcile(sessions, { key: "id" }))
- cleanupDroppedSessionCaches(store, setStore, sessions, setSessionTodo)
- sessionMeta.set(directory, { limit })
- })
- .catch((err) => {
- console.error("Failed to load sessions", err)
- const project = getFilename(directory)
- showToast({
- variant: "error",
- title: language.t("toast.session.listFailed.title", { project }),
- description: formatServerError(err, language.t),
- })
+ const promise = queryClient
+ .ensureQueryData({
+ ...loadSessionsQuery(directory),
+ queryFn: () =>
+ loadRootSessionsWithFallback({
+ directory,
+ limit,
+ list: (query) => globalSDK.client.session.list(query),
+ })
+ .then((x) => {
+ const nonArchived = (x.data ?? [])
+ .filter((s) => !!s?.id)
+ .filter((s) => !s.time?.archived)
+ .sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))
+ const limit = store.limit
+ const childSessions = store.session.filter((s) => !!s.parentID)
+ const sessions = trimSessions([...nonArchived, ...childSessions], {
+ limit,
+ permission: store.permission,
+ })
+ setStore(
+ "sessionTotal",
+ estimateRootSessionTotal({
+ count: nonArchived.length,
+ limit: x.limit,
+ limited: x.limited,
+ }),
+ )
+ setStore("session", reconcile(sessions, { key: "id" }))
+ cleanupDroppedSessionCaches(store, setStore, sessions, setSessionTodo)
+ sessionMeta.set(directory, { limit })
+ })
+ .catch((err) => {
+ console.error("Failed to load sessions", err)
+ const project = getFilename(directory)
+ showToast({
+ variant: "error",
+ title: language.t("toast.session.listFailed.title", { project }),
+ description: formatServerError(err, language.t),
+ })
+ })
+ .then(() => null),
})
+ .then(() => {})
sessionLoads.set(directory, promise)
- promise.finally(() => {
+ void promise.finally(() => {
sessionLoads.delete(directory)
children.unpin(directory)
})
@@ -250,8 +262,9 @@ function createGlobalSync() {
if (pending) return pending
children.pin(directory)
- const promise = (async () => {
+ const promise = Promise.resolve().then(async () => {
const child = children.ensureChild(directory)
+ child[1]("bootstrapPromise", promise!)
const cache = children.vcsCache.get(directory)
if (!cache) return
const sdk = sdkFor(directory)
@@ -269,11 +282,12 @@ function createGlobalSync() {
vcsCache: cache,
loadSessions,
translate: language.t,
+ queryClient,
})
- })()
+ })
booting.set(directory, promise)
- promise.finally(() => {
+ void promise.finally(() => {
booting.delete(directory)
children.unpin(directory)
})
@@ -317,7 +331,7 @@ function createGlobalSync() {
setSessionTodo,
vcsCache: children.vcsCache.get(directory),
loadLsp: () => {
- sdkFor(directory)
+ void sdkFor(directory)
.lsp.status()
.then((x) => {
setStore("lsp", x.data ?? [])
@@ -346,6 +360,7 @@ function createGlobalSync() {
translate: language.t,
formatMoreCount: (count) => language.t("common.moreCountSuffix", { count }),
setGlobalStore: setBootStore,
+ queryClient,
})
bootedAt = Date.now()
} finally {
@@ -359,13 +374,13 @@ function createGlobalSync() {
eventFrame = undefined
eventTimer = setTimeout(() => {
eventTimer = undefined
- globalSDK.event.start()
+ void globalSDK.event.start()
}, 0)
})
} else {
eventTimer = setTimeout(() => {
eventTimer = undefined
- globalSDK.event.start()
+ void globalSDK.event.start()
}, 0)
}
void bootstrap()
diff --git a/packages/app/src/context/global-sync/bootstrap.ts b/packages/app/src/context/global-sync/bootstrap.ts
index 7edd5a1ce..17fe726f9 100644
--- a/packages/app/src/context/global-sync/bootstrap.ts
+++ b/packages/app/src/context/global-sync/bootstrap.ts
@@ -11,13 +11,15 @@ import type {
Todo,
} from "@opencode-ai/sdk/v2/client"
import { showToast } from "@opencode-ai/ui/toast"
-import { getFilename } from "@opencode-ai/util/path"
-import { retry } from "@opencode-ai/util/retry"
+import { getFilename } from "@opencode-ai/shared/util/path"
+import { retry } from "@opencode-ai/shared/util/retry"
import { batch } from "solid-js"
import { reconcile, type SetStoreFunction, type Store } from "solid-js/store"
import type { State, VcsCache } from "./types"
import { cmp, normalizeAgentList, normalizeProviderList } from "./utils"
import { formatServerError } from "@/utils/server-errors"
+import { QueryClient, queryOptions, skipToken } from "@tanstack/solid-query"
+import { loadSessionsQuery } from "../global-sync"
type GlobalStore = {
ready: boolean
@@ -65,28 +67,13 @@ function runAll(list: Array<() => Promise<unknown>>) {
return Promise.allSettled(list.map((item) => item()))
}
-function showErrors(input: {
- errors: unknown[]
- title: string
- translate: (key: string, vars?: Record<string, string | number>) => string
- formatMoreCount: (count: number) => string
-}) {
- if (input.errors.length === 0) return
- const message = formatServerError(input.errors[0], input.translate)
- const more = input.errors.length > 1 ? input.formatMoreCount(input.errors.length - 1) : ""
- showToast({
- variant: "error",
- title: input.title,
- description: message + more,
- })
-}
-
export async function bootstrapGlobal(input: {
globalSDK: OpencodeClient
requestFailedTitle: string
translate: (key: string, vars?: Record<string, string | number>) => string
formatMoreCount: (count: number) => string
setGlobalStore: SetStoreFunction<GlobalStore>
+ queryClient: QueryClient
}) {
const fast = [
() =>
@@ -96,11 +83,16 @@ export async function bootstrapGlobal(input: {
}),
),
() =>
- retry(() =>
- input.globalSDK.provider.list().then((x) => {
- input.setGlobalStore("provider", normalizeProviderList(x.data!))
- }),
- ),
+ input.queryClient.fetchQuery({
+ ...loadProvidersQuery(null),
+ queryFn: () =>
+ retry(() =>
+ input.globalSDK.provider.list().then((x) => {
+ input.setGlobalStore("provider", normalizeProviderList(x.data!))
+ return null
+ }),
+ ),
+ }),
]
const slow = [
@@ -122,20 +114,21 @@ export async function bootstrapGlobal(input: {
}),
),
]
-
- showErrors({
- errors: errors(await runAll(fast)),
- title: input.requestFailedTitle,
- translate: input.translate,
- formatMoreCount: input.formatMoreCount,
- })
+ await runAll(fast)
+ // showErrors({
+ // errors: errors(await runAll(fast)),
+ // title: input.requestFailedTitle,
+ // translate: input.translate,
+ // formatMoreCount: input.formatMoreCount,
+ // })
await waitForPaint()
- showErrors({
- errors: errors(await runAll(slow)),
- title: input.requestFailedTitle,
- translate: input.translate,
- formatMoreCount: input.formatMoreCount,
- })
+ await runAll(slow)
+ // showErrors({
+ // errors: errors(),
+ // title: input.requestFailedTitle,
+ // translate: input.translate,
+ // formatMoreCount: input.formatMoreCount,
+ // })
input.setGlobalStore("ready", true)
}
@@ -187,6 +180,12 @@ function warmSessions(input: {
).then(() => undefined)
}
+export const loadProvidersQuery = (directory: string | null) =>
+ queryOptions<null>({ queryKey: [directory, "providers"], queryFn: skipToken })
+
+export const loadAgentsQuery = (directory: string | null) =>
+ queryOptions<null>({ queryKey: [directory, "agents"], queryFn: skipToken })
+
export async function bootstrapDirectory(input: {
directory: string
sdk: OpencodeClient
@@ -201,6 +200,7 @@ export async function bootstrapDirectory(input: {
project: Project[]
provider: ProviderListResponse
}
+ queryClient: QueryClient
}) {
const loading = input.store.status !== "complete"
const seededProject = projectID(input.directory, input.global.project)
@@ -222,97 +222,7 @@ export async function bootstrapDirectory(input: {
input.setStore("lsp", [])
if (loading) input.setStore("status", "partial")
- const fast = [
- () => retry(() => input.sdk.app.agents().then((x) => input.setStore("agent", normalizeAgentList(x.data)))),
- () => retry(() => input.sdk.config.get().then((x) => input.setStore("config", x.data!))),
- () => retry(() => input.sdk.session.status().then((x) => input.setStore("session_status", x.data!))),
- ]
-
- const slow = [
- () =>
- seededProject
- ? Promise.resolve()
- : retry(() => input.sdk.project.current()).then((x) => input.setStore("project", x.data!.id)),
- () =>
- seededPath
- ? Promise.resolve()
- : retry(() =>
- input.sdk.path.get().then((x) => {
- input.setStore("path", x.data!)
- const next = projectID(x.data?.directory ?? input.directory, input.global.project)
- if (next) input.setStore("project", next)
- }),
- ),
- () =>
- retry(() =>
- input.sdk.vcs.get().then((x) => {
- const next = x.data ?? input.store.vcs
- input.setStore("vcs", next)
- if (next) input.vcsCache.setStore("value", next)
- }),
- ),
- () => retry(() => input.sdk.command.list().then((x) => input.setStore("command", x.data ?? []))),
- () =>
- retry(() =>
- input.sdk.permission.list().then((x) => {
- const ids = (x.data ?? []).map((perm) => perm?.sessionID).filter((id): id is string => !!id)
- const grouped = groupBySession(
- (x.data ?? []).filter((perm): perm is PermissionRequest => !!perm?.id && !!perm.sessionID),
- )
- return warmSessions({ ids, store: input.store, setStore: input.setStore, sdk: input.sdk }).then(() =>
- batch(() => {
- for (const sessionID of Object.keys(input.store.permission)) {
- if (grouped[sessionID]) continue
- input.setStore("permission", sessionID, [])
- }
- for (const [sessionID, permissions] of Object.entries(grouped)) {
- input.setStore(
- "permission",
- sessionID,
- reconcile(
- permissions.filter((p) => !!p?.id).sort((a, b) => cmp(a.id, b.id)),
- { key: "id" },
- ),
- )
- }
- }),
- )
- }),
- ),
- () =>
- retry(() =>
- input.sdk.question.list().then((x) => {
- const ids = (x.data ?? []).map((question) => question?.sessionID).filter((id): id is string => !!id)
- const grouped = groupBySession((x.data ?? []).filter((q): q is QuestionRequest => !!q?.id && !!q.sessionID))
- return warmSessions({ ids, store: input.store, setStore: input.setStore, sdk: input.sdk }).then(() =>
- batch(() => {
- for (const sessionID of Object.keys(input.store.question)) {
- if (grouped[sessionID]) continue
- input.setStore("question", sessionID, [])
- }
- for (const [sessionID, questions] of Object.entries(grouped)) {
- input.setStore(
- "question",
- sessionID,
- reconcile(
- questions.filter((q) => !!q?.id).sort((a, b) => cmp(a.id, b.id)),
- { key: "id" },
- ),
- )
- }
- }),
- )
- }),
- ),
- () => Promise.resolve(input.loadSessions(input.directory)),
- () =>
- retry(() =>
- input.sdk.mcp.status().then((x) => {
- input.setStore("mcp", x.data!)
- input.setStore("mcp_ready", true)
- }),
- ),
- ]
+ const fast = [() => Promise.resolve(input.loadSessions(input.directory))]
const errs = errors(await runAll(fast))
if (errs.length > 0) {
@@ -325,36 +235,138 @@ export async function bootstrapDirectory(input: {
})
}
- await waitForPaint()
- const slowErrs = errors(await runAll(slow))
- if (slowErrs.length > 0) {
- console.error("Failed to finish bootstrap instance", slowErrs[0])
- const project = getFilename(input.directory)
- showToast({
- variant: "error",
- title: input.translate("toast.project.reloadFailed.title", { project }),
- description: formatServerError(slowErrs[0], input.translate),
- })
- }
-
- if (loading && errs.length === 0 && slowErrs.length === 0) input.setStore("status", "complete")
+ ;(async () => {
+ const slow = [
+ () =>
+ input.queryClient.ensureQueryData({
+ ...loadAgentsQuery(input.directory),
+ queryFn: () =>
+ retry(() => input.sdk.app.agents().then((x) => input.setStore("agent", normalizeAgentList(x.data)))).then(
+ () => null,
+ ),
+ }),
+ () => retry(() => input.sdk.config.get().then((x) => input.setStore("config", x.data!))),
+ () => retry(() => input.sdk.session.status().then((x) => input.setStore("session_status", x.data!))),
+ () =>
+ seededProject
+ ? Promise.resolve()
+ : retry(() => input.sdk.project.current()).then((x) => input.setStore("project", x.data!.id)),
+ () =>
+ seededPath
+ ? Promise.resolve()
+ : retry(() =>
+ input.sdk.path.get().then((x) => {
+ input.setStore("path", x.data!)
+ const next = projectID(x.data?.directory ?? input.directory, input.global.project)
+ if (next) input.setStore("project", next)
+ }),
+ ),
+ () =>
+ retry(() =>
+ input.sdk.vcs.get().then((x) => {
+ const next = x.data ?? input.store.vcs
+ input.setStore("vcs", next)
+ if (next) input.vcsCache.setStore("value", next)
+ }),
+ ),
+ () => retry(() => input.sdk.command.list().then((x) => input.setStore("command", x.data ?? []))),
+ () =>
+ retry(() =>
+ input.sdk.permission.list().then((x) => {
+ const ids = (x.data ?? []).map((perm) => perm?.sessionID).filter((id): id is string => !!id)
+ const grouped = groupBySession(
+ (x.data ?? []).filter((perm): perm is PermissionRequest => !!perm?.id && !!perm.sessionID),
+ )
+ return warmSessions({ ids, store: input.store, setStore: input.setStore, sdk: input.sdk }).then(() =>
+ batch(() => {
+ for (const sessionID of Object.keys(input.store.permission)) {
+ if (grouped[sessionID]) continue
+ input.setStore("permission", sessionID, [])
+ }
+ for (const [sessionID, permissions] of Object.entries(grouped)) {
+ input.setStore(
+ "permission",
+ sessionID,
+ reconcile(
+ permissions.filter((p) => !!p?.id).sort((a, b) => cmp(a.id, b.id)),
+ { key: "id" },
+ ),
+ )
+ }
+ }),
+ )
+ }),
+ ),
+ () =>
+ retry(() =>
+ input.sdk.question.list().then((x) => {
+ const ids = (x.data ?? []).map((question) => question?.sessionID).filter((id): id is string => !!id)
+ const grouped = groupBySession((x.data ?? []).filter((q): q is QuestionRequest => !!q?.id && !!q.sessionID))
+ return warmSessions({ ids, store: input.store, setStore: input.setStore, sdk: input.sdk }).then(() =>
+ batch(() => {
+ for (const sessionID of Object.keys(input.store.question)) {
+ if (grouped[sessionID]) continue
+ input.setStore("question", sessionID, [])
+ }
+ for (const [sessionID, questions] of Object.entries(grouped)) {
+ input.setStore(
+ "question",
+ sessionID,
+ reconcile(
+ questions.filter((q) => !!q?.id).sort((a, b) => cmp(a.id, b.id)),
+ { key: "id" },
+ ),
+ )
+ }
+ }),
+ )
+ }),
+ ),
+ () => Promise.resolve(input.loadSessions(input.directory)),
+ () =>
+ retry(() =>
+ input.sdk.mcp.status().then((x) => {
+ input.setStore("mcp", x.data!)
+ input.setStore("mcp_ready", true)
+ }),
+ ),
+ ]
- const rev = (providerRev.get(input.directory) ?? 0) + 1
- providerRev.set(input.directory, rev)
- void retry(() => input.sdk.provider.list())
- .then((x) => {
- if (providerRev.get(input.directory) !== rev) return
- input.setStore("provider", normalizeProviderList(x.data!))
- input.setStore("provider_ready", true)
- })
- .catch((err) => {
- if (providerRev.get(input.directory) !== rev) return
- console.error("Failed to refresh provider list", err)
+ await waitForPaint()
+ const slowErrs = errors(await runAll(slow))
+ if (slowErrs.length > 0) {
+ console.error("Failed to finish bootstrap instance", slowErrs[0])
const project = getFilename(input.directory)
showToast({
variant: "error",
title: input.translate("toast.project.reloadFailed.title", { project }),
- description: formatServerError(err, input.translate),
+ description: formatServerError(slowErrs[0], input.translate),
})
+ }
+
+ if (loading && errs.length === 0 && slowErrs.length === 0) input.setStore("status", "complete")
+
+ const rev = (providerRev.get(input.directory) ?? 0) + 1
+ providerRev.set(input.directory, rev)
+ void input.queryClient.ensureQueryData({
+ ...loadSessionsQuery(input.directory),
+ queryFn: () =>
+ retry(() => input.sdk.provider.list())
+ .then((x) => {
+ if (providerRev.get(input.directory) !== rev) return
+ input.setStore("provider", normalizeProviderList(x.data!))
+ input.setStore("provider_ready", true)
+ })
+ .catch((err) => {
+ if (providerRev.get(input.directory) !== rev) console.error("Failed to refresh provider list", err)
+ const project = getFilename(input.directory)
+ showToast({
+ variant: "error",
+ title: input.translate("toast.project.reloadFailed.title", { project }),
+ description: formatServerError(err, input.translate),
+ })
+ })
+ .then(() => null),
})
+ })()
}
diff --git a/packages/app/src/context/global-sync/child-store.ts b/packages/app/src/context/global-sync/child-store.ts
index 5678491f8..6788e8cc5 100644
--- a/packages/app/src/context/global-sync/child-store.ts
+++ b/packages/app/src/context/global-sync/child-store.ts
@@ -182,6 +182,7 @@ export function createChildStoreManager(input: {
limit: 5,
message: {},
part: {},
+ bootstrapPromise: Promise.resolve(),
})
children[directory] = child
disposers.set(directory, dispose)
@@ -243,8 +244,8 @@ export function createChildStoreManager(input: {
const cached = metaCache.get(directory)
if (!cached) return
const previous = store.projectMeta ?? {}
- const icon = patch.icon ? { ...(previous.icon ?? {}), ...patch.icon } : previous.icon
- const commands = patch.commands ? { ...(previous.commands ?? {}), ...patch.commands } : previous.commands
+ const icon = patch.icon ? { ...previous.icon, ...patch.icon } : previous.icon
+ const commands = patch.commands ? { ...previous.commands, ...patch.commands } : previous.commands
const next = {
...previous,
...patch,
diff --git a/packages/app/src/context/global-sync/event-reducer.ts b/packages/app/src/context/global-sync/event-reducer.ts
index 500013c1d..11a0cf83f 100644
--- a/packages/app/src/context/global-sync/event-reducer.ts
+++ b/packages/app/src/context/global-sync/event-reducer.ts
@@ -1,4 +1,4 @@
-import { Binary } from "@opencode-ai/util/binary"
+import { Binary } from "@opencode-ai/shared/util/binary"
import { produce, reconcile, type SetStoreFunction, type Store } from "solid-js/store"
import type {
Message,
diff --git a/packages/app/src/context/global-sync/queue.ts b/packages/app/src/context/global-sync/queue.ts
index c3468583b..5c228dac0 100644
--- a/packages/app/src/context/global-sync/queue.ts
+++ b/packages/app/src/context/global-sync/queue.ts
@@ -63,6 +63,7 @@ export function createRefreshQueue(input: QueueInput) {
}
} finally {
running = false
+ // oxlint-disable-next-line no-unsafe-finally -- intentional: early return skips schedule() when paused
if (input.paused()) return
if (root || queued.size) schedule()
}
diff --git a/packages/app/src/context/global-sync/types.ts b/packages/app/src/context/global-sync/types.ts
index b0f340a90..28b3705d1 100644
--- a/packages/app/src/context/global-sync/types.ts
+++ b/packages/app/src/context/global-sync/types.ts
@@ -8,7 +8,6 @@ import type {
Part,
Path,
PermissionRequest,
- Project,
ProviderListResponse,
QuestionRequest,
Session,
@@ -73,6 +72,7 @@ export type State = {
part: {
[messageID: string]: Part[]
}
+ bootstrapPromise: Promise<void>
}
export type VcsCache = {
diff --git a/packages/app/src/context/layout.tsx b/packages/app/src/context/layout.tsx
index bab3d39f3..74ea28531 100644
--- a/packages/app/src/context/layout.tsx
+++ b/packages/app/src/context/layout.tsx
@@ -344,7 +344,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
return
}
- setStore("sessionView", sessionKey, "scroll", (prev) => ({ ...(prev ?? {}), ...next }))
+ setStore("sessionView", sessionKey, "scroll", (prev) => ({ ...prev, ...next }))
prune(keep)
},
})
@@ -399,7 +399,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
local?.icon?.color !== undefined
const base = {
- ...(metadata ?? {}),
+ ...metadata,
...project,
icon: {
url: metadata?.icon?.url,
@@ -582,7 +582,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
open(directory: string) {
const root = rootFor(directory)
if (server.projects.list().find((x) => x.worktree === root)) return
- globalSync.project.loadSessions(root)
+ void globalSync.project.loadSessions(root)
server.projects.open(root)
},
close(directory: string) {
diff --git a/packages/app/src/context/local.tsx b/packages/app/src/context/local.tsx
index 1633607de..0b0972ee6 100644
--- a/packages/app/src/context/local.tsx
+++ b/packages/app/src/context/local.tsx
@@ -1,11 +1,10 @@
import { createSimpleContext } from "@opencode-ai/ui/context"
-import { base64Encode } from "@opencode-ai/util/encode"
+import { base64Encode } from "@opencode-ai/shared/util/encode"
import { useParams } from "@solidjs/router"
-import { batch, createEffect, createMemo, onCleanup } from "solid-js"
+import { batch, createEffect, createMemo } from "solid-js"
import { createStore } from "solid-js/store"
import { useModels } from "@/context/models"
import { useProviders } from "@/hooks/use-providers"
-import { modelEnabled, modelProbe } from "@/testing/model-selection"
import { Persist, persisted } from "@/utils/persist"
import { cycleModelVariant, getConfiguredAgentVariant, resolveModelVariant } from "./model-variant"
import { useSDK } from "./sdk"
@@ -388,53 +387,6 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
},
},
}
-
- if (modelEnabled()) {
- const probe = Symbol("model-probe")
-
- modelProbe.bind(probe, {
- setAgent: agent.set,
- setModel: model.set,
- setVariant: model.variant.set,
- })
-
- createEffect(() => {
- const agent = result.agent.current()
- const model = result.model.current()
- modelProbe.set(probe, {
- dir: sdk.directory,
- sessionID: id(),
- last: store.last,
- agent: agent?.name,
- model: model
- ? {
- providerID: model.provider.id,
- modelID: model.id,
- name: model.name,
- }
- : undefined,
- variant: result.model.variant.current() ?? null,
- selected: result.model.variant.selected(),
- configured: result.model.variant.configured(),
- pick: scope(),
- base: undefined,
- current: store.current,
- variants: result.model.variant.list(),
- models: result.model
- .list()
- .filter((item) => result.model.visible({ providerID: item.provider.id, modelID: item.id }))
- .map((item) => ({
- providerID: item.provider.id,
- modelID: item.id,
- name: item.name,
- })),
- agents: result.agent.list().map((item) => ({ name: item.name })),
- })
- })
-
- onCleanup(() => modelProbe.clear(probe))
- }
-
return result
},
})
diff --git a/packages/app/src/context/notification.tsx b/packages/app/src/context/notification.tsx
index 281a1ef33..251b67b06 100644
--- a/packages/app/src/context/notification.tsx
+++ b/packages/app/src/context/notification.tsx
@@ -7,8 +7,8 @@ import { useGlobalSync } from "./global-sync"
import { usePlatform } from "@/context/platform"
import { useLanguage } from "@/context/language"
import { useSettings } from "@/context/settings"
-import { Binary } from "@opencode-ai/util/binary"
-import { base64Encode } from "@opencode-ai/util/encode"
+import { Binary } from "@opencode-ai/shared/util/binary"
+import { base64Encode } from "@opencode-ai/shared/util/encode"
import { decode64 } from "@/utils/base64"
import { EventSessionError } from "@opencode-ai/sdk/v2"
import { Persist, persisted } from "@/utils/persist"
diff --git a/packages/app/src/context/permission-auto-respond.test.ts b/packages/app/src/context/permission-auto-respond.test.ts
index 755611300..2f8ca6265 100644
--- a/packages/app/src/context/permission-auto-respond.test.ts
+++ b/packages/app/src/context/permission-auto-respond.test.ts
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test"
import type { PermissionRequest, Session } from "@opencode-ai/sdk/v2/client"
-import { base64Encode } from "@opencode-ai/util/encode"
+import { base64Encode } from "@opencode-ai/shared/util/encode"
import { autoRespondsPermission, isDirectoryAutoAccepting } from "./permission-auto-respond"
const session = (input: { id: string; parentID?: string }) =>
diff --git a/packages/app/src/context/permission-auto-respond.ts b/packages/app/src/context/permission-auto-respond.ts
index b206deedf..2ebca3434 100644
--- a/packages/app/src/context/permission-auto-respond.ts
+++ b/packages/app/src/context/permission-auto-respond.ts
@@ -1,4 +1,4 @@
-import { base64Encode } from "@opencode-ai/util/encode"
+import { base64Encode } from "@opencode-ai/shared/util/encode"
export function acceptKey(sessionID: string, directory?: string) {
if (!directory) return sessionID
diff --git a/packages/app/src/context/prompt.tsx b/packages/app/src/context/prompt.tsx
index 831fdbca8..9b666e5e7 100644
--- a/packages/app/src/context/prompt.tsx
+++ b/packages/app/src/context/prompt.tsx
@@ -1,5 +1,5 @@
import { createSimpleContext } from "@opencode-ai/ui/context"
-import { checksum } from "@opencode-ai/util/encode"
+import { checksum } from "@opencode-ai/shared/util/encode"
import { useParams } from "@solidjs/router"
import { batch, createMemo, createRoot, getOwner, onCleanup } from "solid-js"
import { createStore, type SetStoreFunction } from "solid-js/store"
diff --git a/packages/app/src/context/sync.tsx b/packages/app/src/context/sync.tsx
index fb02a2d2d..29b7fe68c 100644
--- a/packages/app/src/context/sync.tsx
+++ b/packages/app/src/context/sync.tsx
@@ -1,7 +1,7 @@
import { batch, createMemo } from "solid-js"
import { createStore, produce, reconcile } from "solid-js/store"
-import { Binary } from "@opencode-ai/util/binary"
-import { retry } from "@opencode-ai/util/retry"
+import { Binary } from "@opencode-ai/shared/util/binary"
+import { retry } from "@opencode-ai/shared/util/retry"
import { createSimpleContext } from "@opencode-ai/ui/context"
import {
clearSessionPrefetch,
diff --git a/packages/app/src/context/terminal.tsx b/packages/app/src/context/terminal.tsx
index 17355aab9..31d2d6e04 100644
--- a/packages/app/src/context/terminal.tsx
+++ b/packages/app/src/context/terminal.tsx
@@ -117,7 +117,7 @@ export function clearWorkspaceTerminals(dir: string, sessionIDs?: string[], plat
entry?.value.clear()
}
- removePersisted(Persist.workspace(dir, "terminal"), platform)
+ void removePersisted(Persist.workspace(dir, "terminal"), platform)
const legacy = new Set(getLegacyTerminalStorageKeys(dir))
for (const id of sessionIDs ?? []) {
@@ -126,7 +126,7 @@ export function clearWorkspaceTerminals(dir: string, sessionIDs?: string[], plat
}
}
for (const key of legacy) {
- removePersisted({ key }, platform)
+ void removePersisted({ key }, platform)
}
}