feat(tasks): wake an idle owner when a background task completes

Completion notices went through agent.inject(), which never reserves a
driver, so a task settling after its turn closed left the notice parked
until unrelated input woke the agent — while the same prompt told the
model not to poll for it.

An unreported completion now picks its lane from the owner's state: a
busy owner is injected as before, an idle owner is woken with
followup(). This adopts the delivery rule the subagent continuation
manager already ships. maxConsecutiveWakes bounds the self-exciting
chain and is reset by user-authored input; completionDelivery: quiet
restores the old lane for deterministic transcripts.

Teardown cancellation now claims the terminal report the way kill()
already does, so an owner being destroyed is never woken, and settle()
announces completion last so a reporter that opens a turn synchronously
sees a committed record.
This commit is contained in:
Yichen Jiang
2026-08-11 19:36:49 +08:00
parent 1d12ae62e7
commit d78b796a51
25 changed files with 531 additions and 96 deletions

View File

@@ -41,12 +41,16 @@ declare module '@deepseek-ai/cordis' {
* Implementations must honor these semantics:
* - Registrations outlive producer and controller fibers. Owner and
* service disposal cancel live work and await compliant producers; a
* throwing teardown cancel force-fails only the record.
* throwing teardown cancel force-fails only the record. Teardown
* cancellation also marks the record reported, because a record its owner
* is being destroyed for has no reader left.
* - Owned-task access is fenced by the owner's session id. Ids are
* predictable, so authorization — not secrecy — is the boundary.
* - Settlement is first-wins: one terminal record, one round of contained
* listener notification, and released waiters, even against a late
* producer outcome.
* - Settlement is first-wins: one terminal record, released waiters, and one
* round of contained listener notification, even against a late producer
* outcome. Completion is announced last, after the record is committed and
* every other observer of the settlement has seen it, because a reporter
* may open a model turn synchronously.
* - {@link start} refuses work while no attached task controller 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

View File

@@ -118,8 +118,11 @@ export interface TaskSnapshot {
/** Epoch ms when the task settled; absent while `running`/`stopping`. */
finishedAt?: number
/**
* True when a kill, read, or wait has reported or committed to report the
* terminal state. Completion reporters suppress redundant notices when set.
* True when a kill, read, wait, or teardown cancel has reported or committed
* to report the terminal state. Completion reporters suppress redundant
* notices when set. Teardown claims it because the owner or service being
* destroyed leaves no reader: a reporter that opens a turn on notice would
* otherwise spend a model request per teardown layer.
*/
reported: boolean
}