diff --git a/src/hooks/auto-update-checker/hook.test.ts b/src/hooks/auto-update-checker/hook.test.ts index ecac1f8b1..b7d8e5232 100644 --- a/src/hooks/auto-update-checker/hook.test.ts +++ b/src/hooks/auto-update-checker/hook.test.ts @@ -1,10 +1,23 @@ import type { PluginInput } from "@opencode-ai/plugin" import { describe, expect, mock, test } from "bun:test" -const latestVersionMock = mock.fn(async () => "3.0.1") -const scheduleDeferredIdleCheckMock = mock.fn((runCheck: () => void) => { +let latestVersionCallCount = 0 +let scheduleDeferredIdleCheckCallCount = 0 +const flushMicrotasks = async (count: number): Promise => { + for (let index = 0; index < count; index += 1) { + await Promise.resolve() + } +} + +const latestVersionMock = async () => { + latestVersionCallCount += 1 + return "3.0.1" +} + +const scheduleDeferredIdleCheckMock = (runCheck: () => void) => { + scheduleDeferredIdleCheckCallCount += 1 scheduledCheck = runCheck -}) +} let scheduledCheck: (() => void) | null = null @@ -51,8 +64,8 @@ const createHook = async () => { describe("auto-update-checker hook", () => { test("defers update check until first session idle", async () => { // given - latestVersionMock.mockClear() - scheduleDeferredIdleCheckMock.mockClear() + latestVersionCallCount = 0 + scheduleDeferredIdleCheckCallCount = 0 scheduledCheck = null const hook = await createHook() @@ -60,28 +73,28 @@ describe("auto-update-checker hook", () => { hook.event({ event: { type: "session.created" } }) // then - expect(scheduleDeferredIdleCheckMock).toHaveBeenCalledTimes(0) - expect(latestVersionMock).toHaveBeenCalledTimes(0) + expect(scheduleDeferredIdleCheckCallCount).toBe(0) + expect(latestVersionCallCount).toBe(0) // when hook.event({ event: { type: "session.idle" } }) // then - expect(scheduleDeferredIdleCheckMock).toHaveBeenCalledTimes(1) - expect(latestVersionMock).toHaveBeenCalledTimes(0) + expect(scheduleDeferredIdleCheckCallCount).toBe(1) + expect(latestVersionCallCount).toBe(0) // when - scheduledCheck?.() + await scheduledCheck?.() + await flushMicrotasks(8) // then - expect(latestVersionMock).toHaveBeenCalledTimes(1) + expect(latestVersionCallCount).toBe(1) // when hook.event({ event: { type: "session.idle" } }) - scheduledCheck?.() // then - expect(scheduleDeferredIdleCheckMock).toHaveBeenCalledTimes(1) - expect(latestVersionMock).toHaveBeenCalledTimes(1) + expect(scheduleDeferredIdleCheckCallCount).toBe(1) + expect(latestVersionCallCount).toBe(1) }) }) diff --git a/src/hooks/auto-update-checker/hook.ts b/src/hooks/auto-update-checker/hook.ts index fbe3998da..73f5eed4b 100644 --- a/src/hooks/auto-update-checker/hook.ts +++ b/src/hooks/auto-update-checker/hook.ts @@ -3,6 +3,7 @@ import { log } from "../../shared/logger" import type { AutoUpdateCheckerOptions } from "./types" import { getCachedVersion, getLocalDevVersion } from "./checker" import { runBackgroundUpdateCheck } from "./hook/background-update-check" +import { scheduleDeferredIdleCheck } from "./hook/deferred-idle-check" import { showConfigErrorsIfAny } from "./hook/config-errors-toast" import { updateAndShowConnectedProvidersCacheStatus } from "./hook/connected-providers-status" import { refreshModelCapabilitiesOnStartup } from "./hook/model-capabilities-status" @@ -60,44 +61,45 @@ export function createAutoUpdateCheckerHook( } let hasChecked = false + let hasScheduled = false return { event: ({ event }: { event: { type: string; properties?: unknown } }) => { - if (event.type !== "session.created") return + if (event.type !== "session.idle") return if (isCliRunMode) return - if (hasChecked) return + if (hasChecked || hasScheduled) return - const props = event.properties as { info?: { parentID?: string } } | undefined - if (props?.info?.parentID) return + hasScheduled = true + scheduleDeferredIdleCheck(() => { hasChecked = true + void (async () => { + const cachedVersion = deps.getCachedVersion() + const localDevVersion = deps.getLocalDevVersion(ctx.directory) + const displayVersion = localDevVersion ?? cachedVersion - setTimeout(async () => { - const cachedVersion = deps.getCachedVersion() - const localDevVersion = deps.getLocalDevVersion(ctx.directory) - const displayVersion = localDevVersion ?? cachedVersion + await deps.showConfigErrorsIfAny(ctx) + await deps.updateAndShowConnectedProvidersCacheStatus(ctx) + await deps.refreshModelCapabilitiesOnStartup(modelCapabilities) + await deps.showModelCacheWarningIfNeeded(ctx) - await deps.showConfigErrorsIfAny(ctx) - await deps.updateAndShowConnectedProvidersCacheStatus(ctx) - await deps.refreshModelCapabilitiesOnStartup(modelCapabilities) - await deps.showModelCacheWarningIfNeeded(ctx) - - if (localDevVersion) { - if (showStartupToast) { - deps.showLocalDevToast(ctx, displayVersion, isSisyphusEnabled).catch(() => {}) + if (localDevVersion) { + if (showStartupToast) { + deps.showLocalDevToast(ctx, displayVersion, isSisyphusEnabled).catch(() => {}) + } + deps.log("[auto-update-checker] Local development mode") + return } - deps.log("[auto-update-checker] Local development mode") - return - } - if (showStartupToast) { - deps.showVersionToast(ctx, displayVersion, getToastMessage(false)).catch(() => {}) - } + if (showStartupToast) { + deps.showVersionToast(ctx, displayVersion, getToastMessage(false)).catch(() => {}) + } - deps.runBackgroundUpdateCheck(ctx, autoUpdate, getToastMessage).catch((err) => { - deps.log("[auto-update-checker] Background update check failed:", err) - }) - }, 0) + deps.runBackgroundUpdateCheck(ctx, autoUpdate, getToastMessage).catch((err) => { + deps.log("[auto-update-checker] Background update check failed:", err) + }) + })() + }) }, } } diff --git a/src/hooks/auto-update-checker/hook/deferred-idle-check.ts b/src/hooks/auto-update-checker/hook/deferred-idle-check.ts new file mode 100644 index 000000000..a929cf4ee --- /dev/null +++ b/src/hooks/auto-update-checker/hook/deferred-idle-check.ts @@ -0,0 +1,4 @@ +export function scheduleDeferredIdleCheck(runCheck: () => void): void { + const timeout = setTimeout(runCheck, 5000) + timeout.unref?.() +}