diff --git a/src/renderer/src/store/slices/runtime-status-hydration.ts b/src/renderer/src/store/slices/runtime-status-hydration.ts index b42f05bf535..00f7429645b 100644 --- a/src/renderer/src/store/slices/runtime-status-hydration.ts +++ b/src/renderer/src/store/slices/runtime-status-hydration.ts @@ -1,5 +1,16 @@ import type { PublicKnownRuntimeEnvironment } from '../../../../shared/runtime-environments' +/** Matches the renderer's other catalog-style cache TTLs (checks, work items, Jira). */ +export const RUNTIME_CATALOG_STALE_MS = 60_000 + +let lastCatalogListedAt = 0 + +/** Why: status coverage cannot observe catalog edits made by another client or the + * orca CLI, so an old-enough listing must be re-read even when coverage looks complete. */ +export function isRuntimeCatalogListingStale(): boolean { + return Date.now() - lastCatalogListedAt > RUNTIME_CATALOG_STALE_MS +} + type RuntimeStatusHydrationDependencies = { listEnvironments: () => Promise getCurrentEnvironments: () => PublicKnownRuntimeEnvironment[] @@ -67,6 +78,7 @@ export function createRuntimeStatusHydration({ markCatalogSettled() return } + lastCatalogListedAt = Date.now() if (!revisionsMatch(getCurrentEnvironments(), revisionsAtListStart)) { rerunRequested = true continue diff --git a/src/renderer/src/store/slices/settings.test.ts b/src/renderer/src/store/slices/settings.test.ts index d0397d194de..32082e91e9b 100644 --- a/src/renderer/src/store/slices/settings.test.ts +++ b/src/renderer/src/store/slices/settings.test.ts @@ -10,6 +10,7 @@ import { RUNTIME_PROTOCOL_VERSION } from '../../../../shared/protocol-version' import { clearRuntimeCompatibilityCacheForTests } from '../../runtime/runtime-rpc-client' +import { RUNTIME_CATALOG_STALE_MS } from './runtime-status-hydration' vi.mock('sonner', () => ({ toast: { error: vi.fn(), success: vi.fn(), info: vi.fn() } })) vi.mock('@/lib/agent-status', async (importOriginal) => { @@ -828,4 +829,25 @@ describe('fetchSettings runtime catalog probe', () => { expect(runtimeEnvironmentList).toHaveBeenCalledTimes(1) }) + + it('re-lists the catalog once the listing goes stale even though coverage still matches', async () => { + const environments = [makeRuntimeEnvironment('env-a')] + runtimeEnvironmentList.mockResolvedValue(environments) + const store = createTestStore() + const now = vi.spyOn(Date, 'now').mockReturnValue(1_000_000) + + try { + await store.getState().fetchSettings() + await vi.waitFor(() => expect(store.getState().runtimeStatusByEnvironmentId.size).toBe(1)) + + await store.getState().fetchSettings() + expect(runtimeEnvironmentList).toHaveBeenCalledTimes(1) + + now.mockReturnValue(1_000_000 + RUNTIME_CATALOG_STALE_MS + 1) + await store.getState().fetchSettings() + await vi.waitFor(() => expect(runtimeEnvironmentList).toHaveBeenCalledTimes(2)) + } finally { + now.mockRestore() + } + }) }) diff --git a/src/renderer/src/store/slices/settings.ts b/src/renderer/src/store/slices/settings.ts index 45d3dbc8cd3..fe4d7fcfe38 100644 --- a/src/renderer/src/store/slices/settings.ts +++ b/src/renderer/src/store/slices/settings.ts @@ -14,6 +14,7 @@ import { normalizeTerminalCustomThemes } from '../../../../shared/terminal-custo import { normalizeTaskProviderSettings } from '../../../../shared/task-providers' import { normalizeOpenInApplications } from '../../../../shared/open-in-applications' import { createSettingsSearchState, type SettingsSearchState } from './settings-search-state' +import { isRuntimeCatalogListingStale } from './runtime-status-hydration' import { normalizeDisabledTuiAgents } from '../../../../shared/tui-agent-selection' import { normalizeTuiAgentArgsRecord, @@ -169,7 +170,9 @@ export const createSettingsSlice: StateCreator if ( !runtimeEnvironmentCatalogHydrated || runtimeEnvironmentIds.size !== runtimeStatusByEnvironmentId.size || - runtimeEnvironments.some(({ id }) => !runtimeStatusByEnvironmentId.has(id)) + runtimeEnvironments.some(({ id }) => !runtimeStatusByEnvironmentId.has(id)) || + // Why: coverage is blind to catalog edits from another client or the orca CLI. + isRuntimeCatalogListingStale() ) { void get().hydrateRuntimeEnvironmentStatuses() }