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%.
Address the review bot's five genuinely-new findings on the current code:
- disposal now discards any still-pending inbox items before the loop
exits, so every enqueued id gets a terminal lifecycle event.
- injection (next-step/no-wakeup) validates its payload up front, before
opening the idle one-shot turn, honoring 'invalid input throws before
any append'; and rejects attached contexts (which belong only to inbox
messages) rather than silently dropping them.
- agentMessage() freezes the agent/inbox/* payload so a listener cannot
mutate the shared correlation object mid-dispatch.
- refresh the package READMEs (compact, goal, guard, hook-protocol,
plan-mode, time-context, workspace-context) that still referenced the
removed context/message event, with the source-based user/message
distinction.
The up-front injection validation makes two finally branches unreachable
(v8-ignored as the turn-enclosure backstop). Adds regression tests for
disposal discard, context rejection, up-front validation, and the frozen
payload; per-file coverage stays 100%.
- tui.spec: exercise a goal-sourced injected context card (labels by
source kind, not plugin name), closing the last uncovered branch in
tui/src/index.ts that the CI coverage gate caught.
- time-context.e2e / goal.e2e: filter injected context by source now
that it is a user/message (plugin/goal source), and count goal
continuation rounds by round>0 rather than event type.
Address a second fresh-eye review of the review fixes:
- MAJOR: late steering that lands after runTurn returns terminally
stopped (e.g. during the post-turn flush) was drained by runLoop and
dropped without a discard, leaving a dangling outstanding id the
negative-only invariant can't catch. Emit agent/inbox/discard for it,
symmetric with the in-turn terminal-stop drop.
- remove the dead cancel() idle-settle branch: whenIdle's fast path
already resolves for a lone quiet item, so no waiter is ever left for
it to settle. Document why.
- gen-cordis-api classShape now drops private/protected/#private members
and strips getter/setter bodies, so Session no longer leaks private
fields and getter bodies into the model catalog.
- document that AgentMessage intentionally omits meta (durable-only).
Adds a regression test for the late-steering discard.
Resolve six review findings on the unified-send change:
- quiet (wakeup:false) queued items no longer un-park the driver; the
inbox distinguishes hasWakingQueued (drives the loop, idle/quiescence)
from hasQueued (anything to dequeue), so a lone quiet item parks at idle
and rides the next waking send. whenIdle/cancel settle off the waking
signal, so cancelling a parked quiet item no longer hangs whenIdle.
- SendOptions.meta on queued/steering sends now reaches the durable
user/message and steering/message (was dropped except on injection).
- a terminal agent/turn-stop that drops pending steering emits
agent/inbox/discard so the enqueue-dequeue-or-discard ledger balances.
- the loop-authored continuation reason is snapshotted and frozen like a
public send.
- gen-cordis-api collects exported classes (body-stripped) so the now-
abstract-class Agent and its transitive shapes reappear in the API
catalog.
Adds regression tests for each and re-records the affected snapshot.
Add a branded AgentMessageId assigned to each accepted send message and
returned from send/followup/steer/inject (was void). Rename the inbox
event payload InboxItemInfo to AgentMessage, carrying that id so a caller
can correlate a queued item with its enqueue/dequeue/discard events.
The composer seat lives in a fixed-height conversation column with
overflow hidden, so a long question batch pushed the footer actions out
of reach (review r3635427112). The card now flexes with a viewport-
relative max-height, the option list is the scrollable region (ChatView
list pattern: min-height 0 + overflow-y auto), and header/footer are
flex-shrink 0 so progress, navigation, skip, submit, and cancel stay
reachable.
Verified in headless chromium against this sheet (900x600 viewport,
30-option batch): card capped at 360px (60vh), the option list scrolls
(scrollHeight > clientHeight), and the footer submit/skip buttons stay
inside the viewport. jsdom cannot assert layout; the playwright smoke
follow-up tracks composer scrolling with the existing debt.
AskUserQuestionItem.detail is part of the user-interaction seam contract
but the web frame schema dropped it and the composer never rendered it
(review r3635427108). askUserQuestionItemSchema now forwards detail, the
composer renders it under the title in the description text style, and
the fixture's multi-select question carries one.
ask() already fails EMPTY_QUESTIONS before a request exists, so a
question/requested frame with zero items is host breakage; muxFrameSchema
now refuses it instead of letting an undefined first question reach the
composer (review r3635427102).