fix(agent-loop): third review pass — disposal discard ordering, flush guard, docs

Address a fresh-eye review of the disposal/injection fixes:
- disposal now snapshots, clears, and marks disposed BEFORE emitting
  agent/inbox/discard (mirroring cancel's snapshot→clear→emit), so a
  re-entrant send/cancel from a discard listener throws 'disposed' or
  finds an empty inbox instead of leaking or double-discarding an id.
  The discard is unconditional (even on unpublished setup-rollback) to
  match send's unconditional enqueue, keeping every id balanced.
- restore the turnRecorded guard on the idle-injection flush: a
  turn/start rejected pre-commit (append reentrancy / internal-dispatch
  veto) records nothing and owes no flush; the previous unconditional
  flush emitted a phantom-turn agent/error. The isTurnOpen/turnRecorded
  branches are reachable (reentrant inject from a session/event
  listener) and now covered by a regression test rather than v8-ignored.
- rewrite the agent/inbox/discard event JSDoc to enumerate all three
  emitters (cancel, terminal turn-stop, disposal) — every enqueued id
  gets exactly one terminal dequeue-or-discard.

Per-file coverage stays 100%.
This commit is contained in:
Turtle
2026-07-24 13:38:36 +08:00
parent c7c1b97501
commit d613fdb073
6 changed files with 117 additions and 70 deletions

View File

@@ -334,11 +334,15 @@ declare module 'cordis' {
*/
'agent/inbox/dequeue'(this: Scoped<Agent>, agent: Agent, message: AgentMessage): void
/**
* `cancel()` (without `keepInbox`) dropped pending inbox items without
* delivering them. Fires once per effective clearing call with every
* discarded item, after `agent/cancel-requested` and before the abort.
* @param agent - the agent whose inbox was cleared.
* @param messages - the discarded messages in FIFO order (queued then steering); empty when nothing was pending.
* Pending inbox items were dropped without delivering them, so every
* enqueued id receives exactly one terminal `agent/inbox/dequeue` OR
* `agent/inbox/discard`. Emitters: `cancel()` without `keepInbox` (after
* `agent/cancel-requested`, before the abort); a terminal `agent/turn-stop`
* dropping pending steering (in-turn and on the post-turn late-steering
* drain); and disposal of any still-pending items (before
* `agent/status('disposed')`). Fires once per drop with every dropped item.
* @param agent - the agent whose inbox items were dropped.
* @param messages - the discarded messages in FIFO order (queued then steering); never empty.
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
* @mode emit
*/