fix(auto-update-checker): defer npm registry check until first idle

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
YeonGyu-Kim
2026-04-18 14:12:37 +09:00
parent a03faaa278
commit 3d4299445e
3 changed files with 59 additions and 40 deletions
+27 -14
View File
@@ -1,10 +1,23 @@
import type { PluginInput } from "@opencode-ai/plugin" import type { PluginInput } from "@opencode-ai/plugin"
import { describe, expect, mock, test } from "bun:test" import { describe, expect, mock, test } from "bun:test"
const latestVersionMock = mock.fn(async () => "3.0.1") let latestVersionCallCount = 0
const scheduleDeferredIdleCheckMock = mock.fn((runCheck: () => void) => { let scheduleDeferredIdleCheckCallCount = 0
const flushMicrotasks = async (count: number): Promise<void> => {
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 scheduledCheck = runCheck
}) }
let scheduledCheck: (() => void) | null = null let scheduledCheck: (() => void) | null = null
@@ -51,8 +64,8 @@ const createHook = async () => {
describe("auto-update-checker hook", () => { describe("auto-update-checker hook", () => {
test("defers update check until first session idle", async () => { test("defers update check until first session idle", async () => {
// given // given
latestVersionMock.mockClear() latestVersionCallCount = 0
scheduleDeferredIdleCheckMock.mockClear() scheduleDeferredIdleCheckCallCount = 0
scheduledCheck = null scheduledCheck = null
const hook = await createHook() const hook = await createHook()
@@ -60,28 +73,28 @@ describe("auto-update-checker hook", () => {
hook.event({ event: { type: "session.created" } }) hook.event({ event: { type: "session.created" } })
// then // then
expect(scheduleDeferredIdleCheckMock).toHaveBeenCalledTimes(0) expect(scheduleDeferredIdleCheckCallCount).toBe(0)
expect(latestVersionMock).toHaveBeenCalledTimes(0) expect(latestVersionCallCount).toBe(0)
// when // when
hook.event({ event: { type: "session.idle" } }) hook.event({ event: { type: "session.idle" } })
// then // then
expect(scheduleDeferredIdleCheckMock).toHaveBeenCalledTimes(1) expect(scheduleDeferredIdleCheckCallCount).toBe(1)
expect(latestVersionMock).toHaveBeenCalledTimes(0) expect(latestVersionCallCount).toBe(0)
// when // when
scheduledCheck?.() await scheduledCheck?.()
await flushMicrotasks(8)
// then // then
expect(latestVersionMock).toHaveBeenCalledTimes(1) expect(latestVersionCallCount).toBe(1)
// when // when
hook.event({ event: { type: "session.idle" } }) hook.event({ event: { type: "session.idle" } })
scheduledCheck?.()
// then // then
expect(scheduleDeferredIdleCheckMock).toHaveBeenCalledTimes(1) expect(scheduleDeferredIdleCheckCallCount).toBe(1)
expect(latestVersionMock).toHaveBeenCalledTimes(1) expect(latestVersionCallCount).toBe(1)
}) })
}) })
+28 -26
View File
@@ -3,6 +3,7 @@ import { log } from "../../shared/logger"
import type { AutoUpdateCheckerOptions } from "./types" import type { AutoUpdateCheckerOptions } from "./types"
import { getCachedVersion, getLocalDevVersion } from "./checker" import { getCachedVersion, getLocalDevVersion } from "./checker"
import { runBackgroundUpdateCheck } from "./hook/background-update-check" import { runBackgroundUpdateCheck } from "./hook/background-update-check"
import { scheduleDeferredIdleCheck } from "./hook/deferred-idle-check"
import { showConfigErrorsIfAny } from "./hook/config-errors-toast" import { showConfigErrorsIfAny } from "./hook/config-errors-toast"
import { updateAndShowConnectedProvidersCacheStatus } from "./hook/connected-providers-status" import { updateAndShowConnectedProvidersCacheStatus } from "./hook/connected-providers-status"
import { refreshModelCapabilitiesOnStartup } from "./hook/model-capabilities-status" import { refreshModelCapabilitiesOnStartup } from "./hook/model-capabilities-status"
@@ -60,44 +61,45 @@ export function createAutoUpdateCheckerHook(
} }
let hasChecked = false let hasChecked = false
let hasScheduled = false
return { return {
event: ({ event }: { event: { type: string; properties?: unknown } }) => { event: ({ event }: { event: { type: string; properties?: unknown } }) => {
if (event.type !== "session.created") return if (event.type !== "session.idle") return
if (isCliRunMode) return if (isCliRunMode) return
if (hasChecked) return if (hasChecked || hasScheduled) return
const props = event.properties as { info?: { parentID?: string } } | undefined hasScheduled = true
if (props?.info?.parentID) return
scheduleDeferredIdleCheck(() => {
hasChecked = true hasChecked = true
void (async () => {
const cachedVersion = deps.getCachedVersion()
const localDevVersion = deps.getLocalDevVersion(ctx.directory)
const displayVersion = localDevVersion ?? cachedVersion
setTimeout(async () => { await deps.showConfigErrorsIfAny(ctx)
const cachedVersion = deps.getCachedVersion() await deps.updateAndShowConnectedProvidersCacheStatus(ctx)
const localDevVersion = deps.getLocalDevVersion(ctx.directory) await deps.refreshModelCapabilitiesOnStartup(modelCapabilities)
const displayVersion = localDevVersion ?? cachedVersion await deps.showModelCacheWarningIfNeeded(ctx)
await deps.showConfigErrorsIfAny(ctx) if (localDevVersion) {
await deps.updateAndShowConnectedProvidersCacheStatus(ctx) if (showStartupToast) {
await deps.refreshModelCapabilitiesOnStartup(modelCapabilities) deps.showLocalDevToast(ctx, displayVersion, isSisyphusEnabled).catch(() => {})
await deps.showModelCacheWarningIfNeeded(ctx) }
deps.log("[auto-update-checker] Local development mode")
if (localDevVersion) { return
if (showStartupToast) {
deps.showLocalDevToast(ctx, displayVersion, isSisyphusEnabled).catch(() => {})
} }
deps.log("[auto-update-checker] Local development mode")
return
}
if (showStartupToast) { if (showStartupToast) {
deps.showVersionToast(ctx, displayVersion, getToastMessage(false)).catch(() => {}) deps.showVersionToast(ctx, displayVersion, getToastMessage(false)).catch(() => {})
} }
deps.runBackgroundUpdateCheck(ctx, autoUpdate, getToastMessage).catch((err) => { deps.runBackgroundUpdateCheck(ctx, autoUpdate, getToastMessage).catch((err) => {
deps.log("[auto-update-checker] Background update check failed:", err) deps.log("[auto-update-checker] Background update check failed:", err)
}) })
}, 0) })()
})
}, },
} }
} }
@@ -0,0 +1,4 @@
export function scheduleDeferredIdleCheck(runCheck: () => void): void {
const timeout = setTimeout(runCheck, 5000)
timeout.unref?.()
}