fix(tasks-local): layer control surfaces and listeners by registering scope
One host registry serves every composition in the process, so its two service-wide collections answered per-owner questions process-wide. `start()` asked only whether SOME surface was attached, so an agent whose own composition loads no `tool-tasks` could start work it has no tool to collect or stop as soon as any other preset attached one — and the answer changed depending on which sessions happened to be open. `settle()` walked every registered listener, so a task settling without a waiter injected one completion notice per mounted preset into the same owner. Both collections now sit in `ScopedLayers`, the layered-registry primitive `tools` and `skills` already use: a registration files into its registering context's scope, and a read unions the global layer with the owner's scope chain. A surface or listener registered from an unscoped context lands in the global layer and serves every owner, which is exactly the host-plane composition's own controls, so the TUI path is unchanged without a special case. This supersedes the consumer-side filter in the previous commit. That filter produced the right notices but sat in the wrong layer: it left the `start()` gate process-wide, it could not be enforced against a producer that resolves the registry directly, and it made a Consumer carry scope knowledge that the other layered registries keep in the registry. `tool-tasks` is scope-agnostic again and the `dsh-scope` edge moves to `tasks-local`. `start()`'s refusal is now owner-relative, so its model-visible text names the agent rather than the process. The shipped `minimal` preset keeps `enableRunInBackground: false`, no longer as the safety boundary — the registry owns that now — but so an agent that could never collect a task is not offered the parameter at all. Refs #2141
This commit is contained in:
@@ -44,8 +44,13 @@ declare module 'cordis' {
|
||||
* - Settlement is first-wins: one terminal record, one round of contained
|
||||
* listener notification, and released waiters, even against a late
|
||||
* producer outcome.
|
||||
* - {@link start} refuses work while no control surface is attached, so a
|
||||
* producer cannot start work that callers cannot collect or stop.
|
||||
* - {@link start} refuses work while no attached control surface serves the
|
||||
* spec's owner, so a producer cannot start work that owner cannot collect
|
||||
* or stop. One registry serves every composition in the process, so this
|
||||
* question — and completion-listener delivery — is owner-relative rather
|
||||
* than process-wide: registrations made from an unscoped context serve
|
||||
* every owner, and registrations made under an agent composition's scope
|
||||
* serve exactly the agents composed under it.
|
||||
*/
|
||||
export abstract class TaskService extends Service {
|
||||
constructor(ctx: Context) {
|
||||
@@ -120,17 +125,19 @@ export abstract class TaskService extends Service {
|
||||
abstract wait(id: TaskId, timeoutMs: number, caller?: Agent, signal?: AbortSignal): Promise<TaskSnapshot>
|
||||
|
||||
/**
|
||||
* Register an effect-scoped completion listener. Each listener is contained;
|
||||
* returned promises are observed but not awaited. No listener runs after
|
||||
* service disposal.
|
||||
* Register an effect-scoped completion listener. It receives the settlements
|
||||
* of the owners its registering context's scope covers; each listener is
|
||||
* contained; returned promises are observed but not awaited. No listener runs
|
||||
* after service disposal.
|
||||
* @param listener - receives each terminal snapshot and its exact owner.
|
||||
* @returns disposer that unregisters the listener.
|
||||
*/
|
||||
abstract onTaskDone(listener: TaskDoneListener): () => void
|
||||
|
||||
/**
|
||||
* Attach an effect-scoped surface that can read and stop tasks. {@link start}
|
||||
* refuses work while none is attached.
|
||||
* Attach an effect-scoped surface that can read and stop tasks. It serves the
|
||||
* owners its registering context's scope covers, and {@link start} refuses an
|
||||
* owner no attached surface serves.
|
||||
* @param name - diagnostic label; duplicate names remain independent.
|
||||
* @returns disposer that detaches this surface.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user