From a89239e29e36b097ccafb2874fd80d7ae72be070 Mon Sep 17 00:00:00 2001 From: a1chzt Date: Mon, 13 Apr 2026 21:36:16 +0800 Subject: [PATCH 1/3] fix: clear stale cloud model on disconnect and hide plan credits row MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After cloud disconnect, the defaultModelId still pointed to a cloud/link model, allowing the bot to keep using it. Three fixes: 1. cloud-disconnect route now calls ensureValidDefaultModel() + syncAll() so BYOK fallback works (matches cloud-profile/disconnect pattern) 2. disconnectDesktopCloud() clears defaultModelId when it was a cloud model, preventing stale refs when no BYOK is available 3. onCloudStateChanged restarts OpenClaw on both connect and disconnect (was connect-only) to flush cached provider credentials Also removes the plan credits (套餐积分) row from the sidebar balance popup. --- apps/controller/src/app/container.ts | 2 +- .../src/routes/desktop-compat-routes.ts | 8 +++++-- .../controller/src/store/nexu-config-store.ts | 11 ++++++++++ apps/web/src/layouts/workspace-layout.tsx | 22 ------------------- 4 files changed, 18 insertions(+), 25 deletions(-) diff --git a/apps/controller/src/app/container.ts b/apps/controller/src/app/container.ts index f5b2bbdeb..187135771 100644 --- a/apps/controller/src/app/container.ts +++ b/apps/controller/src/app/container.ts @@ -156,7 +156,7 @@ export async function createContainer(): Promise { // auto-switching the default model during startup or first-channel connect. configStore.onCloudStateChanged = async (change) => { await openclawSyncService.syncAll(); - if (!change.hadCloudInventory && change.hasCloudInventory) { + if (change.hadCloudInventory !== change.hasCloudInventory) { await openclawProcess.stop(); openclawProcess.enableAutoRestart(); openclawProcess.start(); diff --git a/apps/controller/src/routes/desktop-compat-routes.ts b/apps/controller/src/routes/desktop-compat-routes.ts index f188c3f8c..93f4704bb 100644 --- a/apps/controller/src/routes/desktop-compat-routes.ts +++ b/apps/controller/src/routes/desktop-compat-routes.ts @@ -301,8 +301,12 @@ export function registerDesktopCompatRoutes( }, }, }), - async (c) => - c.json(await container.desktopLocalService.disconnectCloud(), 200), + async (c) => { + const result = await container.desktopLocalService.disconnectCloud(); + await container.modelProviderService.ensureValidDefaultModel(); + const { configPushed } = await container.openclawSyncService.syncAll(); + return c.json({ ...result, configPushed }, 200); + }, ); app.openapi( diff --git a/apps/controller/src/store/nexu-config-store.ts b/apps/controller/src/store/nexu-config-store.ts index f733911f6..1b3a51393 100644 --- a/apps/controller/src/store/nexu-config-store.ts +++ b/apps/controller/src/store/nexu-config-store.ts @@ -2688,6 +2688,12 @@ export class NexuConfigStore { const previousCloud = readDesktopCloud(await this.getConfig()); this.abortDesktopCloudPolling(); + const config = await this.getConfig(); + const currentModelId = config.runtime.defaultModelId; + const wasCloudModel = + resolveManagedCloudModel(currentModelId, previousCloud.models ?? []) !== + null; + await this.setDesktopCloudState({ connected: false, polling: false, @@ -2699,6 +2705,11 @@ export class NexuConfigStore { apiKey: null, models: [], }); + + if (wasCloudModel) { + await this.setDefaultModel(""); + } + await this.onCloudStateChanged?.({ hadCloudInventory: (previousCloud.models?.length ?? 0) > 0, hasCloudInventory: false, diff --git a/apps/web/src/layouts/workspace-layout.tsx b/apps/web/src/layouts/workspace-layout.tsx index b08336aef..47d6a49fd 100644 --- a/apps/web/src/layouts/workspace-layout.tsx +++ b/apps/web/src/layouts/workspace-layout.tsx @@ -1085,28 +1085,6 @@ function WorkspaceLayoutInner() { {sidebarCreditBreakdown.giftedBalance} -
- - {t("layout.sidebar.balancePopup.recharged")} - - - - {t( - "layout.sidebar.balancePopup.rechargedTooltip", - )} - - - - - {sidebarCreditBreakdown.planBalance} - -