fix(agent-loop): address review — quiet-item parking, meta, discard balance
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.
This commit is contained in:
@@ -201,7 +201,10 @@ export class ReactLoopAgent extends Agent {
|
||||
id: AgentMessageId, content: ContentBlock[], source: MessageSource, wakeup: boolean, options?: SendOptions,
|
||||
): InboxMessage {
|
||||
const contexts = options?.contexts ?? []
|
||||
const accepted = snapshotJsonValue({ id, content, source, contexts, wakeup })
|
||||
const accepted = snapshotJsonValue({
|
||||
id, content, source, contexts, wakeup,
|
||||
...options?.meta !== undefined ? { meta: options.meta } : {},
|
||||
})
|
||||
if (accepted === undefined) {
|
||||
throw new TypeError('agent message content, source, and contexts must be losslessly JSON-serializable')
|
||||
}
|
||||
@@ -344,6 +347,11 @@ export class ReactLoopAgent extends Agent {
|
||||
agentEvents(this.loopCtx, this).emit('agent/cancel-requested', resolvedCause)
|
||||
}
|
||||
if (!keepInbox) {
|
||||
// Whether the parked driver was already scheduled to run: a waking item
|
||||
// woke `waitForQueued`, so the loop WILL resume and settle idle waiters
|
||||
// itself through the pre-run-cancel path (possibly after a replacement
|
||||
// prompt). Only a lone quiet item leaves the loop truly parked.
|
||||
const willResume = this.#inbox.hasWakingQueued
|
||||
// Snapshot before clearing so the discard notification carries the exact
|
||||
// dropped items; a replacement synchronously enqueued by an
|
||||
// `agent/cancel-requested` observer belongs to the next turn, not here.
|
||||
@@ -354,6 +362,15 @@ export class ReactLoopAgent extends Agent {
|
||||
const items = discarded.map(({ message, steering }) => agentMessage(message, steering))
|
||||
agentEvents(this.loopCtx, this).emit('agent/inbox/discard', items)
|
||||
}
|
||||
// Clearing a parked quiet (`wakeup:false`) item reaches quiescence with no
|
||||
// status transition and without waking the parked driver, so settle any
|
||||
// `whenIdle` waiter here. When a waking item was present the loop resumes
|
||||
// and settles itself; while `running` (including the post-turn flush
|
||||
// window) the driver still owns the eventual idle transition. So settle
|
||||
// only for a parked, non-running agent whose sole cleared work was quiet.
|
||||
if (cancellation === undefined && !willResume && this._status !== 'running') {
|
||||
this.settleIdleWaiters()
|
||||
}
|
||||
}
|
||||
cancellation?.request(resolvedCause)
|
||||
}
|
||||
@@ -365,7 +382,9 @@ export class ReactLoopAgent extends Agent {
|
||||
*/
|
||||
whenIdle(): Promise<void> {
|
||||
if (this._status === 'disposed') return this.done
|
||||
if (this._status !== 'running' && !this.#inbox.hasQueued) return Promise.resolve()
|
||||
// A lone quiet (`wakeup:false`) queued item leaves the agent quiescent — the
|
||||
// driver stays parked — so gate on hasWakingQueued, not hasQueued.
|
||||
if (this._status !== 'running' && !this.#inbox.hasWakingQueued) return Promise.resolve()
|
||||
// Agent-owned waiters survive concurrent fiber disposal.
|
||||
return new Promise<void>((resolve) => {
|
||||
this.idleWaiters.push(() => {
|
||||
|
||||
Reference in New Issue
Block a user