fix(tasks-local): return the layer disposer directly and cover scoped teardown

`ScopedLayers.effect` already returns an exact `() => void`, so the inherited
`() => void dispose()` wrapper voided a void — two lint rules, four errors.

The scoped layer's own teardown had no test, which is the registry-contribution
disposal contract the testing policy requires and the only path that calls
`TaskLayer.isEmpty()`: `ScopedLayers` prunes a scope's layer when its last
contribution disposes. The new case mounts one plugin contributing both a
surface and a listener into one scope, then unloads it and observes that the
agents which joined that scope are refused again.

Refs #2141
This commit is contained in:
Yichen Jiang
2026-08-10 16:34:29 +08:00
parent 109059429a
commit cf5fbd02b8
2 changed files with 26 additions and 4 deletions

View File

@@ -238,23 +238,21 @@ export class LocalTaskService extends TaskService {
}
onTaskDone(listener: TaskDoneListener): () => void {
const dispose = this.layers.effect(
return this.layers.effect(
this.ctx,
layer => layer.listeners.append(listener),
{ label: 'tasks.onTaskDone()' },
)
return () => void dispose()
}
attachSurface(name: string): () => void {
// One token per call keeps duplicate labels independently disposable.
const token = Symbol(name)
const dispose = this.layers.effect(
return this.layers.effect(
this.ctx,
layer => layer.surfaces.append(token),
{ label: 'tasks.attachSurface()' },
)
return () => void dispose()
}
/**