fix(skills): throttle focus rescans and correct self-blocked placement copy

Every window focus re-read and re-hashed all installed packages, and the
nudge and panel each forced their own trailing rescan for one event; a
15s cooldown plus a shared invalidation latch keep one bounded scan per
event while install-change events stay immediate. Bundle artifacts are
now loaded once per run instead of re-parsed on every scan. A read-only
or otherwise unsupported outdated placement now explains that it blocks
itself instead of blaming a phantom sibling placement; the supported
topology set moved to shared so eligibility and copy cannot drift.
This commit is contained in:
Brennan Benson
2026-07-14 03:13:50 -07:00
parent 008a20d0df
commit 29fd88ad1c
12 changed files with 139 additions and 23 deletions
+18 -1
View File
@@ -30,9 +30,26 @@ function assertSupportedSchema(
}
}
export async function loadSkillBundleArtifacts(
const artifactsByResourceRoot = new Map<string, Promise<SkillBundleArtifacts>>()
// Why: the artifacts ship with the binary and never change within a run, while
// focus-triggered rescans would otherwise re-read and re-parse them every time.
export function loadSkillBundleArtifacts(
resourceRoot = app.isPackaged ? process.resourcesPath : resolve(process.cwd(), 'resources')
): Promise<SkillBundleArtifacts> {
const cached = artifactsByResourceRoot.get(resourceRoot)
if (cached) {
return cached
}
const loading = readSkillBundleArtifacts(resourceRoot)
artifactsByResourceRoot.set(resourceRoot, loading)
loading.catch(() => {
artifactsByResourceRoot.delete(resourceRoot)
})
return loading
}
async function readSkillBundleArtifacts(resourceRoot: string): Promise<SkillBundleArtifacts> {
const bundleRoot = join(resourceRoot, 'skills')
const [manifest, registry, releaseMapping] = await Promise.all([
readFile(join(bundleRoot, 'current-manifest.json'), 'utf8').then(JSON.parse),
@@ -1,13 +1,8 @@
import type {
SkillFreshnessInstallation,
SkillInstallationTopology
import {
SUPPORTED_GLOBAL_SKILL_TOPOLOGIES,
type SkillFreshnessInstallation
} from '../../shared/skill-freshness'
const SUPPORTED_GLOBAL_TOPOLOGIES = new Set<SkillInstallationTopology>([
'canonical-copy',
'provider-alias'
])
export function eligibleSkillUpdateNames(
installations: readonly SkillFreshnessInstallation[]
): string[] {
@@ -24,7 +19,7 @@ export function eligibleSkillUpdateNames(
const everyPlacementIsSafe = entries.every(
(entry) =>
(entry.status === 'current' || entry.status === 'outdated') &&
SUPPORTED_GLOBAL_TOPOLOGIES.has(entry.topology) &&
SUPPORTED_GLOBAL_SKILL_TOPOLOGIES.has(entry.topology) &&
Boolean(entry.resolvedPath && entry.physicalIdentity)
)
if (hasOutdated && everyPlacementIsSafe) {
@@ -191,4 +191,18 @@ describe('SkillFreshnessPanel', () => {
expect(rendered.textContent).toContain('Repository scope')
expect(rendered.textContent).not.toContain('Review update command')
})
it('explains a self-blocked outdated placement without blaming a sibling', async () => {
mocks.inventory = {
schemaVersion: 1,
installations: [placement('orca-cli', { topology: 'read-only' })],
eligibleUpdateNames: [],
scannedAt: 1
}
const rendered = await renderPanel()
expect(rendered.textContent).toContain('this placement cannot be updated safely in place')
expect(rendered.textContent).not.toContain('another placement of this name')
expect(rendered.textContent).toContain('Read only')
})
})
@@ -5,7 +5,10 @@ import type {
SkillFreshnessStatus,
SkillInstallationTopology
} from '../../../../shared/skill-freshness'
import { buildTargetedSkillUpdateCommand } from '../../../../shared/skill-freshness'
import {
buildTargetedSkillUpdateCommand,
SUPPORTED_GLOBAL_SKILL_TOPOLOGIES
} from '../../../../shared/skill-freshness'
import { useSkillFreshness } from '@/hooks/useSkillFreshness'
import { notifyInstalledAgentSkillsChanged } from '@/hooks/useInstalledAgentSkills'
import { translate } from '@/i18n/i18n'
@@ -68,15 +71,23 @@ function statusDescription(
'Exactly matches the version bundled with this Orca build.'
)
case 'outdated':
return eligibleNames.has(installation.name)
if (eligibleNames.has(installation.name)) {
return translate(
'auto.components.skills.SkillFreshnessPanel.outdatedDescription',
'Exactly matches an older official Orca snapshot.'
)
}
// Why: the block can come from this row's own unsupported placement or from
// a sibling placement of the same name; blaming a phantom sibling misleads.
return SUPPORTED_GLOBAL_SKILL_TOPOLOGIES.has(installation.topology)
? translate(
'auto.components.skills.SkillFreshnessPanel.outdatedDescription',
'Exactly matches an older official Orca snapshot.'
)
: translate(
'auto.components.skills.SkillFreshnessPanel.outdatedBlockedDescription',
'An older official copy was found, but another placement of this name prevents a safe global update.'
)
: translate(
'auto.components.skills.SkillFreshnessPanel.outdatedUnsupportedPlacementDescription',
'An older official copy was found, but this placement cannot be updated safely in place.'
)
case 'newer-known':
return translate(
'auto.components.skills.SkillFreshnessPanel.newerKnownDescription',
@@ -71,4 +71,51 @@ describe('useSkillFreshness', () => {
await act(async () => second.resolve(inventory(2)))
expect(state?.inventory?.scannedAt).toBe(2)
})
it('skips focus rescans inside the cooldown but honors install-change events', async () => {
const first = deferred<SkillFreshnessInventory>()
const second = deferred<SkillFreshnessInventory>()
const freshnessInventory = vi
.fn()
.mockReturnValueOnce(first.promise)
.mockReturnValueOnce(second.promise)
window.api = { skills: { freshnessInventory } } as never
await act(async () => root?.render(<Probe />))
await act(async () => first.resolve(inventory(1)))
expect(freshnessInventory).toHaveBeenCalledTimes(1)
await act(async () => window.dispatchEvent(new Event('focus')))
expect(freshnessInventory).toHaveBeenCalledTimes(1)
await act(async () => window.dispatchEvent(new Event('orca:installed-agent-skills-changed')))
await act(async () => second.resolve(inventory(2)))
expect(freshnessInventory).toHaveBeenCalledTimes(2)
expect(state?.inventory?.scannedAt).toBe(2)
})
it('coalesces multiple consumers into one rescan per invalidation event', async () => {
const first = deferred<SkillFreshnessInventory>()
const second = deferred<SkillFreshnessInventory>()
const freshnessInventory = vi
.fn()
.mockReturnValueOnce(first.promise)
.mockReturnValue(second.promise)
window.api = { skills: { freshnessInventory } } as never
await act(async () =>
root?.render(
<>
<Probe />
<Probe />
</>
)
)
await act(async () => first.resolve(inventory(1)))
expect(freshnessInventory).toHaveBeenCalledTimes(1)
await act(async () => window.dispatchEvent(new Event('orca:installed-agent-skills-changed')))
await act(async () => second.resolve(inventory(2)))
expect(freshnessInventory).toHaveBeenCalledTimes(2)
})
})
+26 -6
View File
@@ -3,10 +3,15 @@ import type { SkillFreshnessInventory } from '../../../shared/skill-freshness'
import { useMountedRef } from './useMountedRef'
const INSTALLED_SKILLS_CHANGED_EVENT = 'orca:installed-agent-skills-changed'
// Why: window focus fires on every alt-tab, and each scan re-reads and re-hashes
// every installed package; a just-completed scan stays authoritative briefly.
const FOCUS_RESCAN_COOLDOWN_MS = 15_000
let cachedInventory: SkillFreshnessInventory | null = null
let pendingInventory: Promise<SkillFreshnessInventory> | null = null
let invalidationRevision = 0
let completedRevision = -1
let lastCompletedScanAt = 0
let sharedInvalidationPending = false
async function loadInventory(force: boolean): Promise<SkillFreshnessInventory> {
if (force) {
@@ -24,6 +29,7 @@ async function loadInventory(force: boolean): Promise<SkillFreshnessInventory> {
.then((inventory) => {
cachedInventory = inventory
completedRevision = Math.max(completedRevision, requestRevision)
lastCompletedScanAt = Date.now()
return inventory
})
.finally(() => {
@@ -77,15 +83,27 @@ export function useSkillFreshness(): SkillFreshnessState {
}, [refresh])
useEffect(() => {
const invalidate = (): void => {
const invalidate = (respectCooldown: boolean): void => {
if (respectCooldown && Date.now() - lastCompletedScanAt < FOCUS_RESCAN_COOLDOWN_MS) {
return
}
// Why: the nudge and the panel both listen for the same events; one forced
// rescan per event refreshes every consumer through the shared request.
const alreadyInvalidated = sharedInvalidationPending
sharedInvalidationPending = true
queueMicrotask(() => {
sharedInvalidationPending = false
})
cachedInventory = null
void refresh(true)
void refresh(!alreadyInvalidated)
}
window.addEventListener('focus', invalidate)
window.addEventListener(INSTALLED_SKILLS_CHANGED_EVENT, invalidate)
const onFocus = (): void => invalidate(true)
const onInstalledSkillsChanged = (): void => invalidate(false)
window.addEventListener('focus', onFocus)
window.addEventListener(INSTALLED_SKILLS_CHANGED_EVENT, onInstalledSkillsChanged)
return () => {
window.removeEventListener('focus', invalidate)
window.removeEventListener(INSTALLED_SKILLS_CHANGED_EVENT, invalidate)
window.removeEventListener('focus', onFocus)
window.removeEventListener(INSTALLED_SKILLS_CHANGED_EVENT, onInstalledSkillsChanged)
}
}, [refresh])
@@ -98,5 +116,7 @@ export const _skillFreshnessCacheForTests = {
pendingInventory = null
invalidationRevision = 0
completedRevision = -1
lastCompletedScanAt = 0
sharedInvalidationPending = false
}
}
+1
View File
@@ -3618,6 +3618,7 @@
"currentDescription": "Exactly matches the version bundled with this Orca build.",
"outdatedDescription": "Exactly matches an older official Orca snapshot.",
"outdatedBlockedDescription": "An older official copy was found, but another placement of this name prevents a safe global update.",
"outdatedUnsupportedPlacementDescription": "An older official copy was found, but this placement cannot be updated safely in place.",
"newerKnownDescription": "Matches a known revision newer than this Orca build. No update is offered.",
"unrecognizedDescription": "May be edited or from another source. Orca will not update it.",
"inaccessibleDescription": "Orca could not inspect this placement. No update is offered.",
+1
View File
@@ -3618,6 +3618,7 @@
"currentDescription": "Coincide exactamente con la versión incluida en esta compilación de Orca.",
"outdatedDescription": "Coincide exactamente con una instantánea oficial anterior de Orca.",
"outdatedBlockedDescription": "Se encontró una copia oficial anterior, pero otra ubicación con este nombre impide una actualización global segura.",
"outdatedUnsupportedPlacementDescription": "Se encontró una copia oficial anterior, pero esta ubicación no se puede actualizar de forma segura.",
"newerKnownDescription": "Coincide con una revisión conocida más reciente que esta compilación de Orca. No se ofrece ninguna actualización.",
"unrecognizedDescription": "Puede estar editada o proceder de otra fuente. Orca no la actualizará.",
"inaccessibleDescription": "Orca no pudo inspeccionar esta ubicación. No se ofrece ninguna actualización.",
+1
View File
@@ -3618,6 +3618,7 @@
"currentDescription": "この Orca ビルドに同梱されたバージョンと完全に一致します。",
"outdatedDescription": "以前の公式 Orca スナップショットと完全に一致します。",
"outdatedBlockedDescription": "以前の公式コピーが見つかりましたが、同名の別の配置があるため安全なグローバル更新はできません。",
"outdatedUnsupportedPlacementDescription": "以前の公式コピーが見つかりましたが、この配置はその場で安全に更新できません。",
"newerKnownDescription": "この Orca ビルドより新しい既知のリビジョンと一致します。更新は提供されません。",
"unrecognizedDescription": "編集済みか、別の提供元の可能性があります。Orca は更新しません。",
"inaccessibleDescription": "Orca はこの配置を検査できませんでした。更新は提供されません。",
+1
View File
@@ -3618,6 +3618,7 @@
"currentDescription": "이 Orca 빌드에 포함된 버전과 정확히 일치합니다.",
"outdatedDescription": "이전 공식 Orca 스냅샷과 정확히 일치합니다.",
"outdatedBlockedDescription": "이전 공식 복사본을 찾았지만 같은 이름의 다른 위치 때문에 안전한 전역 업데이트를 제공할 수 없습니다.",
"outdatedUnsupportedPlacementDescription": "이전 공식 복사본을 찾았지만 이 위치는 제자리에서 안전하게 업데이트할 수 없습니다.",
"newerKnownDescription": "이 Orca 빌드보다 최신인 알려진 리비전과 일치합니다. 업데이트는 제공되지 않습니다.",
"unrecognizedDescription": "편집되었거나 다른 출처에서 왔을 수 있습니다. Orca는 업데이트하지 않습니다.",
"inaccessibleDescription": "Orca가 이 위치를 검사할 수 없습니다. 업데이트는 제공되지 않습니다.",
+2 -1
View File
@@ -3618,6 +3618,7 @@
"currentDescription": "与此 Orca 构建中捆绑的版本完全一致。",
"outdatedDescription": "与早期的官方 Orca 快照完全一致。",
"outdatedBlockedDescription": "找到了早期官方副本,但同名的另一个位置阻止了安全的全局更新。",
"outdatedUnsupportedPlacementDescription": "找到了早期官方副本,但此位置无法安全地就地更新。",
"newerKnownDescription": "与比此 Orca 构建更新的已知修订版一致。不提供更新。",
"unrecognizedDescription": "可能已被编辑或来自其他来源。Orca 不会更新它。",
"inaccessibleDescription": "Orca 无法检查此位置。不提供更新。",
@@ -11427,7 +11428,7 @@
"d5edd81755": "重命名自",
"6e459867ad": "会话本地连续性状态。 Git 不再将此文件报告为未合并。",
"9c2901ef8a": "下一次冲突",
"41d9af2e7a": "上一个冲突",
"41d9af2e7a": "上一个冲突",
"55d61a0ccd": "冲突 ·",
"da539359b6": "对于此冲突,没有可编辑的工作树文件。"
},
+7
View File
@@ -56,6 +56,13 @@ export type SkillInstallationTopology =
| 'repo-scope'
| 'plugin-cache'
// Why: eligibility and the explanation copy must agree on which placements the
// validated npx rail can converge; a drifted copy would blame a phantom sibling.
export const SUPPORTED_GLOBAL_SKILL_TOPOLOGIES: ReadonlySet<SkillInstallationTopology> = new Set([
'canonical-copy',
'provider-alias'
])
export type SkillFreshnessInstallation = {
id: string
name: string