From a6f841293beee453b37c1d8da15f105bbc68230e Mon Sep 17 00:00:00 2001 From: zipg Date: Thu, 24 Sep 2026 19:20:27 +0800 Subject: [PATCH] fix(updates): include MCP updates in toolbar update check --- apps/desktop/src/App.vue | 7 ++++--- .../__tests__/useComponentUpdates.spec.ts | 21 +++++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src/App.vue b/apps/desktop/src/App.vue index 2f05b1d8aa..f1f2a4701d 100644 --- a/apps/desktop/src/App.vue +++ b/apps/desktop/src/App.vue @@ -1264,11 +1264,12 @@ function openPluginConnectionDialog(pluginId: string, providerId: string) { showConnectionDialog.value = true; } async function checkAllUpdates() { - if (manualCheckingAllUpdates.value) return; + if (manualCheckingAllUpdates.value || componentUpdates.updating.value) return; manualCheckingAllUpdates.value = true; const startedAt = Date.now(); try { - const [, componentRefresh] = await Promise.allSettled([checkUpdates({ silent: true }), componentUpdates.refresh()]); + // A user-triggered check must replace any in-flight background snapshot that may predate the update. + const [, componentRefresh] = await Promise.allSettled([checkUpdates({ silent: true }), componentUpdates.refresh({ force: true })]); if (componentRefresh.status === "fulfilled" && componentRefresh.value) syncToolbarComponentUpdateState(); const remaining = 500 - (Date.now() - startedAt); if (remaining > 0) await new Promise((resolve) => setTimeout(resolve, remaining)); @@ -1284,7 +1285,7 @@ function openDriverStoreFromUpdate(target?: DriverStoreTab) { function handleToolbarUpdateClick() { showUpdateDialog.value = true; - if (!checkingAllUpdates.value) void checkAllUpdates(); + void checkAllUpdates(); } function syncToolbarComponentUpdateState() { diff --git a/apps/desktop/src/composables/__tests__/useComponentUpdates.spec.ts b/apps/desktop/src/composables/__tests__/useComponentUpdates.spec.ts index 2f7f12f0c7..ba95f4d321 100644 --- a/apps/desktop/src/composables/__tests__/useComponentUpdates.spec.ts +++ b/apps/desktop/src/composables/__tests__/useComponentUpdates.spec.ts @@ -108,6 +108,27 @@ describe("useComponentUpdates", () => { expect(updates.driverUpdateCount.value).toBe(0); }); + it("starts a fresh MCP check for an explicit user action instead of reusing a background result", async () => { + const backgroundMcp = deferred(); + const userMcp = deferred(); + mocks.checkMcpServerStatus.mockReturnValueOnce(backgroundMcp.promise).mockReturnValueOnce(userMcp.promise); + const updates = useComponentUpdates({ isDesktop: true }); + + const backgroundRefresh = updates.refresh(); + await Promise.resolve(); + const userRefresh = updates.refresh({ force: true }); + await Promise.resolve(); + + userMcp.resolve(mcpStatus); + expect(await userRefresh).toBe(true); + expect(updates.mcpUpdateAvailable.value).toBe(true); + + backgroundMcp.resolve({ ...mcpStatus, latest_version: mcpStatus.current_version, update_available: false }); + expect(await backgroundRefresh).toBe(false); + expect(updates.mcpUpdateAvailable.value).toBe(true); + expect(mocks.checkMcpServerStatus).toHaveBeenCalledTimes(2); + }); + it("clears driver and MCP update state after a successful update and refresh", async () => { mocks.listInstalledAgents.mockResolvedValueOnce([{ db_type: "mysql", update_available: true }]).mockResolvedValueOnce([{ db_type: "mysql", update_available: false }]); mocks.checkMcpServerStatus.mockResolvedValueOnce(mcpStatus).mockResolvedValueOnce({ ...mcpStatus, latest_version: "1.0.0", update_available: false });