fix(tool-tasks): claim completion notices only for the mount's own scope

Moving the task registry to the host plane put every preset's `tool-tasks`
listener on ONE `LocalTaskService`. `settle()` computes a single snapshot and
walks every registered listener with no scope filter, and it marks `reported`
only when a waiter is present — so a task settling without a waiter reached
each mount's listener with `reported` false and every one of them injected the
same completion into the same owner. Three shipped presets carry `tool-tasks`,
and a preset file edit adds a second generation of the same mount, so an agent
read N copies of one notice as model-visible durable context.

A mount now claims an owner only when the owner's scope chain reaches the
mount's own scope. An unscoped mount is the host-plane instance that serves
every agent, which keeps the TUI composition and every existing test intact.

Registry-side ownership was the alternative: mark `reported` once the first
listener claims it. It is wrong because `onTaskDone` is not a notice-only
seam — the `dsh-tasks` invariant companion registers a validating listener —
so first-claim-wins would silence observers that are not delivering anything.

The regression test mounts two scoped `tool-tasks` over one registry and
settles an unowned-wait task, which is the only path that reaches the notice
listeners at all: the shipped-composition e2e uses `wait: true`, and a waiter
marks `reported` before settlement, so that test structurally cannot cover it.

Also corrects the standing-mounts Agent Note, which still listed `tasks-local`
among the stateful PRESET plugins.

Refs #2141
This commit is contained in:
Yichen Jiang
2026-08-10 15:27:10 +08:00
parent 02d45ed004
commit 37ebe87087
17 changed files with 88 additions and 15 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/module-graph.md
module-graph.md: c41db02165740b19a9ef751e6f50316a76df28c3
module-graph.zh.md: 9071dbc0f2e6df8ec7edd1f3e14cd7ff063123fa
module-graph.md: 0f76bfd2dd700d81e2c6fb6faec3d2c0c9655e98
module-graph.zh.md: 7fa0eb72666e63e72109a272cdc9ce323c60d2fd