diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx index 211969fde7..366731b832 100644 --- a/packages/app/src/app.tsx +++ b/packages/app/src/app.tsx @@ -12,14 +12,9 @@ import { ThemeProvider } from "@opencode-ai/ui/theme/context" import { MetaProvider } from "@solidjs/meta" import { type BaseRouterProps, Navigate, Route, Router } from "@solidjs/router" import { QueryClient, QueryClientProvider } from "@tanstack/solid-query" -import { Effect } from "effect" import { - batch, type Component, - createEffect, createMemo, - createResource, - createSignal, ErrorBoundary, For, type JSX, @@ -51,7 +46,6 @@ import { WslServersProvider } from "@/context/wsl-servers" import DirectoryLayout from "@/pages/directory-layout" import Layout from "@/pages/layout" import { ErrorPage } from "./pages/error" -import { useCheckServerHealth } from "./utils/server-health" const HomeRoute = lazy(() => import("@/pages/home")) const loadSession = () => import("@/pages/session") @@ -167,47 +161,7 @@ export function AppBaseProviders(props: ParentProps<{ locale?: Locale }>) { function ConnectionGate(props: ParentProps<{ disableHealthCheck?: boolean }>) { const server = useServer() - const checkServerHealth = useCheckServerHealth() - - const [checkMode, setCheckMode] = createSignal<"blocking" | "background">("blocking") - const healthTarget = createMemo(() => { - const current = server.current - if (props.disableHealthCheck || !current) return "" - return [ - ServerConnection.key(current), - current.type, - current.http.url, - current.http.username ?? "", - current.http.password ?? "", - ].join("\n") - }) - - createEffect(() => { - healthTarget() - setCheckMode("blocking") - }) - - // performs repeated health check with a grace period for - // non-http connections, otherwise fails instantly - const [startupHealthCheck, healthCheckActions] = createResource( - healthTarget, - () => - props.disableHealthCheck - ? true - : Effect.gen(function* () { - if (!server.current) return true - const { http, type } = server.current - while (true) { - const res = yield* Effect.promise(() => checkServerHealth(http)) - if (res.healthy) return true - if (checkMode() === "background" || type === "http") return false - } - }).pipe( - Effect.timeoutOrElse({ duration: "10 seconds", orElse: () => Effect.succeed(false) }), - Effect.ensuring(Effect.sync(() => setCheckMode("background"))), - Effect.runPromise, - ), - ) + const healthy = createMemo(() => props.disableHealthCheck || server.healthy()) const splash = (
@@ -218,23 +172,14 @@ function ConnectionGate(props: ParentProps<{ disableHealthCheck?: boolean }>) { return ( - + { - if (checkMode() === "background") void healthCheckActions.refetch() - }} onServerSelected={(key) => { startTransition(() => { - batch(() => { - setCheckMode("blocking") - server.setActive(key) - }) + server.setActive(key) }) }} /> @@ -248,7 +193,7 @@ function ConnectionGate(props: ParentProps<{ disableHealthCheck?: boolean }>) { ) } -function ConnectionError(props: { onRetry?: () => void; onServerSelected?: (key: ServerConnection.Key) => void }) { +function ConnectionError(props: { onServerSelected?: (key: ServerConnection.Key) => void }) { const dialog = useDialog() const language = useLanguage() const platform = usePlatform() @@ -259,9 +204,6 @@ function ConnectionError(props: { onRetry?: () => void; onServerSelected?: (key: const unreachable = createMemo(() => language.t("app.server.unreachable", { server: serverToken }).split(serverToken)) const canManage = createMemo(() => server.current?.type === "sidecar" && server.current?.variant === "wsl") - const timer = setInterval(() => props.onRetry?.(), 1000) - onCleanup(() => clearInterval(timer)) - return (
diff --git a/packages/app/src/components/dialog-select-server.tsx b/packages/app/src/components/dialog-select-server.tsx index a684a350e9..4762dddc41 100644 --- a/packages/app/src/components/dialog-select-server.tsx +++ b/packages/app/src/components/dialog-select-server.tsx @@ -12,7 +12,6 @@ import { batch, createEffect, createMemo, onCleanup, Show, startTransition, untr import { createStore, reconcile } from "solid-js/store" import { DialogWslServer } from "@/components/dialog-wsl-server" import { ServerHealthIndicator, ServerRow } from "@/components/server/server-row" -import { useDefaultServer } from "@/context/default-server" import { useLanguage } from "@/context/language" import { usePlatform } from "@/context/platform" import { normalizeServerUrl, ServerConnection, useServer } from "@/context/server" @@ -147,7 +146,6 @@ export function DialogSelectServer(props: DialogSelectServerProps = {}) { const server = useServer() const platform = usePlatform() const language = useLanguage() - const { defaultKey, canDefault, setDefault } = useDefaultServer() const wslServers = useWslServers() const checkServerHealth = useCheckServerHealth() let disposed = false @@ -274,7 +272,7 @@ export function DialogSelectServer(props: DialogSelectServerProps = {}) { }, onSuccess: async (key) => { server.remove(key) - if (defaultKey() === key) await setDefault(null) + if (server.defaultKey() === key) await server.setDefault(null) }, onError: (err) => showRequestError(language, err), })) @@ -549,7 +547,7 @@ export function DialogSelectServer(props: DialogSelectServerProps = {}) { async function handleRemove(key: ServerConnection.Key) { server.remove(key) - if (defaultKey() === key) await setDefault(null) + if (server.defaultKey() === key) await server.setDefault(null) } function handleRemoveWsl(conn: ServerConnection.Any) { @@ -621,7 +619,7 @@ export function DialogSelectServer(props: DialogSelectServerProps = {}) { const wsl = isWslSidecar(i) const wslDistro = wsl ? i.distro : undefined const blocked = () => health(key)?.healthy === false - const canChangeDefault = () => canDefault() && i.type !== "ssh" + const canChangeDefault = () => server.canDefault() && i.type !== "ssh" const canRemove = () => i.type === "http" || wsl const hasMenuActionsBeforeDelete = () => canRemove() && (i.type === "http" || canChangeDefault() || canRetryWsl(i)) const outdated = () => { @@ -651,7 +649,7 @@ export function DialogSelectServer(props: DialogSelectServerProps = {}) { version={wslCheck(i)?.version ?? undefined} class="flex items-center gap-3 min-w-0 flex-1" badge={ - + {language.t("dialog.server.status.default")} @@ -708,15 +706,15 @@ export function DialogSelectServer(props: DialogSelectServerProps = {}) { Retry start - - void setDefault(key)}> + + void server.setDefault(key)}> {language.t("dialog.server.menu.default")} - - void setDefault(null)}> + + void server.setDefault(null)}> {language.t("dialog.server.menu.defaultRemove")} diff --git a/packages/app/src/components/status-popover-body.tsx b/packages/app/src/components/status-popover-body.tsx index b290f7b5d5..3c7dbad1d9 100644 --- a/packages/app/src/components/status-popover-body.tsx +++ b/packages/app/src/components/status-popover-body.tsx @@ -6,17 +6,13 @@ import { Tabs } from "@opencode-ai/ui/tabs" import { useMutation } from "@tanstack/solid-query" import { showToast } from "@opencode-ai/ui/toast" import { useNavigate } from "@solidjs/router" -import { type Accessor, batch, createEffect, createMemo, For, type JSXElement, onCleanup, Show, startTransition, untrack } from "solid-js" -import { createStore, reconcile } from "solid-js/store" -import { ServerHealthIndicator, ServerRow } from "@/components/server/server-row" -import { useDefaultServer } from "@/context/default-server" +import { type Accessor, batch, createEffect, createMemo, For, type JSXElement, onCleanup, Show, startTransition } from "solid-js" +import { createStore } from "solid-js/store" +import { ServerRow } from "@/components/server/server-row" import { useLanguage } from "@/context/language" import { useSDK } from "@/context/sdk" import { ServerConnection, useServer } from "@/context/server" import { useSync } from "@/context/sync" -import { useCheckServerHealth, type ServerHealth } from "@/utils/server-health" - -const pollMs = 10_000 const pluginEmptyMessage = (value: string, file: string): JSXElement => { const parts = value.split(file) @@ -30,72 +26,6 @@ const pluginEmptyMessage = (value: string, file: string): JSXElement => { ) } -const listServersByHealth = ( - list: ServerConnection.Any[], - active: ServerConnection.Key | undefined, - status: Record, -) => { - if (!list.length) return list - const order = new Map(list.map((url, index) => [url, index] as const)) - const rank = (value?: ServerHealth) => { - if (value?.healthy === true) return 0 - if (value?.healthy === false) return 2 - return 1 - } - - return list.slice().sort((a, b) => { - if (ServerConnection.key(a) === active) return -1 - if (ServerConnection.key(b) === active) return 1 - const diff = rank(status[ServerConnection.key(a)]) - rank(status[ServerConnection.key(b)]) - if (diff !== 0) return diff - return (order.get(a) ?? 0) - (order.get(b) ?? 0) - }) -} - -const useServerHealth = (servers: Accessor, enabled: Accessor) => { - const checkServerHealth = useCheckServerHealth() - const [status, setStatus] = createStore({} as Record) - const pollKey = createMemo(() => - enabled() - ? servers() - .map((conn) => - [ServerConnection.key(conn), conn.http.url, conn.http.username ?? "", conn.http.password ?? ""].join("\n"), - ) - .join("\n\n") - : "", - ) - - createEffect(() => { - if (!enabled()) { - setStatus(reconcile({})) - return - } - pollKey() - const list = untrack(servers) - let dead = false - - const refresh = async () => { - const results: Record = {} - await Promise.all( - list.map(async (conn) => { - results[ServerConnection.key(conn)] = await checkServerHealth(conn.http) - }), - ) - if (dead) return - setStatus(reconcile(results)) - } - - void refresh() - const id = setInterval(() => void refresh(), pollMs) - onCleanup(() => { - dead = true - clearInterval(id) - }) - }) - - return status -} - const useMcpToggleMutation = () => { const sync = useSync() const sdk = useSDK() @@ -125,7 +55,6 @@ export function StatusPopoverBody(props: { shown: Accessor }) { const language = useLanguage() const navigate = useNavigate() const sdk = useSDK() - const defaultServer = useDefaultServer() const [load, setLoad] = createStore({ lspDone: false, @@ -193,8 +122,6 @@ export function StatusPopoverBody(props: { shown: Accessor }) { if (list.every((item) => ServerConnection.key(item) !== ServerConnection.key(current))) return [current, ...list] return [current, ...list.filter((item) => ServerConnection.key(item) !== ServerConnection.key(current))] }) - const health = useServerHealth(servers, props.shown) - const sortedServers = createMemo(() => listServersByHealth(servers(), server.key, health)) const toggleMcp = useMcpToggleMutation() const mcpNames = createMemo(() => Object.keys(sync.data.mcp ?? {}).sort((a, b) => a.localeCompare(b))) const mcpStatus = (name: string) => sync.data.mcp?.[name]?.status @@ -219,7 +146,7 @@ export function StatusPopoverBody(props: { shown: Accessor }) { > - {sortedServers().length > 0 ? `${sortedServers().length} ` : ""} + {servers().length > 0 ? `${servers().length} ` : ""} {language.t("status.popover.tab.servers")} @@ -239,45 +166,35 @@ export function StatusPopoverBody(props: { shown: Accessor }) {
- + {(s) => { const key = ServerConnection.key(s) - const blocked = () => health[key]?.healthy === false return (