fix(skill): ignore unchanged missing-root probes
This commit is contained in:
@@ -421,7 +421,7 @@ class SkillWatchManager {
|
||||
|
||||
private openAncestorWatcher(state: RootWatchState, mode: Extract<RootWatchMode, { kind: 'ancestor' }>): WatchHandle {
|
||||
const listener = (_current: Stats, _previous: Stats): void => {
|
||||
this.handleWatchEvent(state, mode, 'change', mode.nextPath)
|
||||
void this.handleAncestorWatchEvent(state, mode)
|
||||
}
|
||||
watchFile(mode.nextPath, {
|
||||
persistent: false,
|
||||
@@ -435,6 +435,25 @@ class SkillWatchManager {
|
||||
}
|
||||
}
|
||||
|
||||
private async handleAncestorWatchEvent(
|
||||
state: RootWatchState,
|
||||
mode: Extract<RootWatchMode, { kind: 'ancestor' }>,
|
||||
): Promise<void> {
|
||||
let current: RootWatchMode
|
||||
try {
|
||||
current = await resolveRootWatchMode(state.root.path)
|
||||
} catch (error) {
|
||||
/* v8 ignore start -- Non-absence stat failures need a platform permission or I/O fault. */
|
||||
if (!this.closing && state.owners.size > 0) this.handleWatcherError(state, error)
|
||||
return
|
||||
/* v8 ignore stop */
|
||||
}
|
||||
if (this.closing || state.owners.size === 0 || sameWatchMode(mode, current)) return
|
||||
this.queueInvalidation()
|
||||
state.unhealthy = true
|
||||
this.scheduleRewatch(state)
|
||||
}
|
||||
|
||||
private async openRootWatcher(state: RootWatchState, mode: Extract<RootWatchMode, { kind: 'root' }>): Promise<WatchHandle> {
|
||||
const watcher = chokidar.watch(mode.anchor, {
|
||||
persistent: false,
|
||||
@@ -481,13 +500,13 @@ class SkillWatchManager {
|
||||
|
||||
private handleWatchEvent(
|
||||
state: RootWatchState,
|
||||
mode: RootWatchMode,
|
||||
mode: Extract<RootWatchMode, { kind: 'root' }>,
|
||||
event: SkillWatchEvent,
|
||||
path: string,
|
||||
): void {
|
||||
if (this.closing || !isRelevantWatchEvent(state.root, mode, event, resolve(path))) return
|
||||
this.queueInvalidation()
|
||||
if (mode.kind === 'ancestor' || (resolve(path) === state.root.path && event === 'unlinkDir')) {
|
||||
if (resolve(path) === state.root.path && event === 'unlinkDir') {
|
||||
state.unhealthy = true
|
||||
this.scheduleRewatch(state)
|
||||
}
|
||||
@@ -591,13 +610,10 @@ function sameWatchMode(left: RootWatchMode, right: RootWatchMode): boolean {
|
||||
|
||||
function isRelevantWatchEvent(
|
||||
root: SkillRoot,
|
||||
mode: RootWatchMode,
|
||||
mode: Extract<RootWatchMode, { kind: 'root' }>,
|
||||
event: SkillWatchEvent,
|
||||
path: string,
|
||||
): boolean {
|
||||
if (mode.kind === 'ancestor') {
|
||||
return path === mode.nextPath
|
||||
}
|
||||
const segments = containedSegments(root.path, path)
|
||||
if (segments === undefined) return false
|
||||
if (segments.length === 0) return event === 'addDir' || event === 'unlinkDir'
|
||||
|
||||
@@ -92,6 +92,31 @@ beforeEach(() => {
|
||||
})
|
||||
|
||||
describe('skill-local watcher failures', () => {
|
||||
it('ignores missing-path probes until the observed path actually changes', async () => {
|
||||
const home = await tempDir('skill-watch-missing-stable')
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SkillService)
|
||||
const fiber = await ctx.plugin(SkillLocal, {
|
||||
dshHome: join(home, '.dsh'),
|
||||
agentsHome: join(home, '.agents'),
|
||||
watch: true,
|
||||
watchPollIntervalMs: 10,
|
||||
})
|
||||
expect(await ctx.skills.snapshot()).toEqual({ skills: [], complete: true })
|
||||
expect(watcherHarness.watchFiles).toHaveLength(2)
|
||||
let invalidations = 0
|
||||
ctx.on('skills/change', () => { invalidations += 1 })
|
||||
|
||||
for (const control of watcherHarness.watchFiles) {
|
||||
control.listener({} as Stats, {} as Stats)
|
||||
}
|
||||
await settle()
|
||||
|
||||
expect(invalidations).toBe(0)
|
||||
expect(watcherHarness.watchFiles).toHaveLength(2)
|
||||
await fiber.dispose()
|
||||
})
|
||||
|
||||
it('marks a startup failure incomplete and retries discovery without caching it', async () => {
|
||||
const home = await tempDir('skill-watch-start-error')
|
||||
const root = join(home, '.dsh/skills')
|
||||
|
||||
Reference in New Issue
Block a user