diff --git a/packages/tasks/tasks-local/src/index.ts b/packages/tasks/tasks-local/src/index.ts index 146d3e4ca7..cb7de27ecd 100644 --- a/packages/tasks/tasks-local/src/index.ts +++ b/packages/tasks/tasks-local/src/index.ts @@ -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() } /** diff --git a/packages/tasks/tasks-local/tests/tasks.spec.ts b/packages/tasks/tasks-local/tests/tasks.spec.ts index bdaa31d975..6a7eb44d09 100644 --- a/packages/tasks/tasks-local/tests/tasks.spec.ts +++ b/packages/tasks/tasks-local/tests/tasks.spec.ts @@ -805,6 +805,30 @@ describe('LocalTaskService disposal', () => { expect(ownerEffects()).toHaveLength(0) }) + it('drops a scoped layer when its registrations dispose', async () => { + const ctx = new Context() + await ctx.plugin(AgentRegistry) + await ctx.plugin(LocalTaskService) + const standing = createScope(ctx, {}) + // One mount contributes both kinds into the same layer, as `tool-tasks` + // does; unloading it must leave nothing serving the agents that joined it. + const mount = await standing.ctx.plugin({ + inject: ['tasks'], + apply(pluginCtx: Context) { + pluginCtx.tasks.attachSurface('tool-tasks') + pluginCtx.tasks.onTaskDone(() => {}) + }, + }) + const owner = stubAgent(ctx, 'joined', scopeOf(standing.ctx)) + ctx.agents.register(owner) + expect(() => ctx.tasks.start(producer({ owner }).spec)).not.toThrow() + + await mount.dispose() + + expect(() => ctx.tasks.start(producer({ owner }).spec)) + .toThrow('no control surface serves this agent') + }) + it('detaching the last surface re-arms the register fence', async () => { const ctx = new Context() await ctx.plugin(LocalTaskService)