From 1adc655cc4e5874ae8615c1300b695eac7c6cfff Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Fri, 24 Jul 2026 12:08:08 +0800 Subject: [PATCH] fix(app): gate legacy server features --- packages/app/src/app.tsx | 60 +++++++++++++++---- .../src/components/dialog-custom-provider.tsx | 1 + .../app/src/components/settings-general.tsx | 52 ++++++++-------- .../app/src/components/settings-providers.tsx | 57 ++++++++++-------- .../src/components/settings-v2/general.tsx | 54 +++++++++-------- .../src/components/settings-v2/providers.tsx | 57 ++++++++++-------- .../src/components/status-popover-body.tsx | 50 +++++++++------- packages/app/src/context/layout.tsx | 2 +- packages/app/src/context/permission.tsx | 1 + packages/app/src/context/server-sdk.tsx | 13 +++- packages/app/src/context/settings.tsx | 9 ++- .../session/timeline/message-timeline.tsx | 5 +- .../pages/session/use-session-commands.tsx | 5 ++ 13 files changed, 228 insertions(+), 138 deletions(-) diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx index 25d2e3749a..42284ee2c4 100644 --- a/packages/app/src/app.tsx +++ b/packages/app/src/app.tsx @@ -47,7 +47,7 @@ import { ServerSyncProvider, useServerSync } from "@/context/server-sync" import { GlobalProvider, useGlobal } from "@/context/global" import { HighlightsProvider } from "@/context/highlights" import { LanguageProvider, type Locale, useLanguage } from "@/context/language" -import { LayoutProvider } from "@/context/layout" +import { currentRoute, LayoutProvider } from "@/context/layout" import { ModelsProvider } from "@/context/models" import { NotificationProvider } from "@/context/notification" import { PermissionProvider } from "@/context/permission" @@ -237,6 +237,44 @@ function UiI18nBridge(props: ParentProps) { return {props.children} } +function LayoutCompatibility(props: ParentProps) { + const global = useGlobal() + const location = useLocation() + const server = useServer() + const settings = useSettings() + const tabs = useTabs() + const connection = createMemo(() => { + const route = currentRoute(location.pathname, location.search) + if (route.type === "draft" && !tabs.ready()) return + const key = + route.type === "draft" + ? tabs.store.find((tab): tab is DraftTab => tab.type === "draft" && tab.draftID === route.draftID)?.server + : route.type === "home" + ? undefined + : route.server + if (!key) return server.current + return global.servers.list().find((item) => ServerConnection.key(item) === key) + }) + const protocol = createMemo(() => { + const value = connection() + if (!value) return + return global.ensureServerCtx(value).sdk.protocolKind() + }) + + createEffect(() => { + const value = protocol() + if (!value) return + settings.general.setNewLayoutRequired(value === "v2") + }) + const ready = createMemo(() => { + const value = protocol() + if (!value) return false + return settings.general.newLayoutRequired() === (value === "v2") + }) + + return {props.children} +} + declare global { interface Window { __OPENCODE__?: { @@ -558,15 +596,17 @@ export function AppInterface(props: { component={props.router ?? Router} root={(routerProps) => ( - - - - - {routerProps.children} - - - - + + + + + + {routerProps.children} + + + + + )} > diff --git a/packages/app/src/components/dialog-custom-provider.tsx b/packages/app/src/components/dialog-custom-provider.tsx index 9e04cd83ad..5db684a2da 100644 --- a/packages/app/src/components/dialog-custom-provider.tsx +++ b/packages/app/src/components/dialog-custom-provider.tsx @@ -131,6 +131,7 @@ export function CustomProviderForm(props: { autofocus?: boolean } = {}) { const saveMutation = useMutation(() => ({ mutationFn: async (result: NonNullable>) => { + if ((await serverSDK().protocol) !== "v1") throw new Error("Custom providers are unavailable on this server") const disabledProviders = serverSync().data.config.disabled_providers ?? [] const nextDisabled = disabledProviders.filter((id) => id !== result.providerID) diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx index 3beb97225a..3bd4cd10cc 100644 --- a/packages/app/src/components/settings-general.tsx +++ b/packages/app/src/components/settings-general.tsx @@ -13,7 +13,7 @@ import { useLanguage } from "@/context/language" import { usePermission } from "@/context/permission" import { usePlatform, type DisplayBackend } from "@/context/platform" import { useServerSync } from "@/context/server-sync" -import { useServerSDK } from "@/context/server-sdk" +import { useServerProtocol, useServerSDK } from "@/context/server-sdk" import { useUpdaterAction } from "./updater-action" import { monoDefault, @@ -125,10 +125,12 @@ export const SettingsGeneral: Component = () => { const serverSync = useServerSync() const serverSdk = useServerSDK() + const protocol = useServerProtocol() const [shells] = createResource( - () => - serverSdk() + () => (protocol() === "v1" ? serverSdk() : undefined), + (sdk) => + sdk .client.pty.shells() .then((res) => res.data ?? []) .catch(() => [] as ShellOption[]), @@ -322,27 +324,29 @@ export const SettingsGeneral: Component = () => { - - o.value === currentShell()) ?? autoOption} + value={(o) => o.id} + label={(o) => o.label} + onSelect={(option) => { + if (!option) return + if (option.value === currentShell()) return + serverSync().updateConfig({ shell: option.value }) + }} + variant="secondary" + size="small" + triggerVariant="settings" + triggerStyle={{ "min-width": "180px" }} + /> + + void }> = (props) => const dialog = useDialog() const language = useLanguage() const serverSDK = useServerSDK() + const protocol = useServerProtocol() const serverSync = useServerSync() const providers = useProviders() const providerConnect = useProviderConnectController({ onBack: props.onBack }) @@ -83,7 +84,8 @@ const SettingsProvidersContent: Component<{ onBack?: () => void }> = (props) => return language.t("settings.providers.tag.other") } - const canDisconnect = (item: ProviderItem) => source(item) !== "env" + const canDisconnect = (item: ProviderItem) => + source(item) !== "env" && (protocol() === "v1" || !isConfigCustom(item.id)) const note = (id: string) => PROVIDER_NOTES.find((item) => item.match(id))?.key @@ -96,6 +98,7 @@ const SettingsProvidersContent: Component<{ onBack?: () => void }> = (props) => } const disableProvider = async (providerID: string, name: string) => { + if (protocol() !== "v1") return const before = serverSync().data.config.disabled_providers ?? [] const next = before.includes(providerID) ? before : [...before, providerID] serverSync().set("config", "disabled_providers", next) @@ -218,31 +221,33 @@ const SettingsProvidersContent: Component<{ onBack?: () => void }> = (props) => )} -
-
-
- - {language.t("provider.custom.title")} - {language.t("settings.providers.tag.custom")} -
- - {language.t("settings.providers.custom.description")} - -
- -
+
+
+ + {language.t("provider.custom.title")} + {language.t("settings.providers.tag.custom")} +
+ + {language.t("settings.providers.custom.description")} + +
+ + +