refactor(agent): publish agent/inbox/claimed from Inbox.claim
Move the claimed-message notification loop out of the loop's pre-step into Inbox.claim(target, turn), so the step-boundary operation publishes its own claimed notifications like insertions and discards do.
This commit is contained in:
@@ -81,6 +81,7 @@ export class ReactLoopAgent implements Agent {
|
||||
this.inbox = new Inbox(session, {
|
||||
inserted: (message) => { emitAgentEvent(loopCtx, this, 'agent/inbox/inserted', { message }) },
|
||||
discarded: (message) => { emitAgentEvent(loopCtx, this, 'agent/inbox/discarded', { message }) },
|
||||
claimed: (message, turn) => { emitAgentEvent(loopCtx, this, 'agent/inbox/claimed', { message, turn }) },
|
||||
})
|
||||
const lastTurn = session.events.findLast(event => event.type === 'turn/start')?.data.turn ?? 0
|
||||
this.phase = { kind: 'idle', lastTurn }
|
||||
@@ -198,10 +199,7 @@ export class ReactLoopAgent implements Agent {
|
||||
/* v8 ignore next -- private callers establish the running phase before proposing a step */
|
||||
if (this.phase.kind !== 'running') throw new Error(`agent "${this.id}": pre-step outside running phase`)
|
||||
const signal = this.phase.abort.signal
|
||||
const claimed = this.inbox.claim(target)
|
||||
for (const message of claimed) {
|
||||
emitAgentEvent(this.loopCtx, this, 'agent/inbox/claimed', { message, turn: position.turn })
|
||||
}
|
||||
const claimed = this.inbox.claim(target, position.turn)
|
||||
const assembly = await this.loopCtx.systemPrompt.assemble(assembleContextFor(this, signal))
|
||||
signal.throwIfAborted()
|
||||
const context = this.runtimeContext.project(renderContextSnapshot(assembly))
|
||||
|
||||
@@ -19,6 +19,8 @@ export interface InboxNotifications {
|
||||
inserted(message: UserMessage): void
|
||||
/** Publish one discarded message. */
|
||||
discarded(message: UserMessage): void
|
||||
/** Publish one claimed message inside its owning turn. */
|
||||
claimed(message: UserMessage, turn: number): void
|
||||
}
|
||||
|
||||
/** A replay-once projection that incrementally consumes later inbox splices. */
|
||||
@@ -61,17 +63,19 @@ export class Inbox {
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove and return the complete batch proposed for one step. The durable
|
||||
* splices are pure deletions; the caller publishes claimed notifications.
|
||||
* Remove and return the complete batch proposed for one step, publishing
|
||||
* each claimed message. The durable splices are pure deletions.
|
||||
* @param target - whether this boundary also consumes one queued turn.
|
||||
* @param turn - turn that will own the claimed batch.
|
||||
* @returns next-step input followed by the queued turn, when requested.
|
||||
* @internal - the agent loop's step-boundary operation, not a plugin seam.
|
||||
*/
|
||||
claim(target: InboxTarget): UserMessage[] {
|
||||
claim(target: InboxTarget, turn: number): UserMessage[] {
|
||||
const claimed = this.mutate('next-step', 0, this.nextStep.length, [], false)
|
||||
if (target === 'next-turn') {
|
||||
claimed.push(...this.mutate('next-turn', 0, 1, [], false))
|
||||
}
|
||||
for (const message of claimed) this.notifications.claimed(message, turn)
|
||||
return claimed
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ function stubAgent(rawId: string, overrides: Partial<Agent> = {}): Agent {
|
||||
id,
|
||||
options: {},
|
||||
session,
|
||||
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {} }),
|
||||
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} }),
|
||||
status: 'idle',
|
||||
ctx: new Context(),
|
||||
send: () => {},
|
||||
@@ -45,7 +45,7 @@ describe('Inbox', () => {
|
||||
inserted: [],
|
||||
})
|
||||
|
||||
expect(() => new Inbox(session, { inserted: () => {}, discarded: () => {} }))
|
||||
expect(() => new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} }))
|
||||
.toThrow('invalid persisted inbox splice at session seq 0')
|
||||
})
|
||||
|
||||
@@ -54,6 +54,7 @@ describe('Inbox', () => {
|
||||
const inserted: UserMessage[] = []
|
||||
const discarded: UserMessage[] = []
|
||||
const inbox = new Inbox(session, {
|
||||
claimed: () => {},
|
||||
inserted: message => void inserted.push(message),
|
||||
discarded: message => void discarded.push(message),
|
||||
})
|
||||
@@ -92,7 +93,7 @@ describe('Inbox', () => {
|
||||
|
||||
it('normalizes splice coordinates, rejects duplicate identities, and reports missing removals', () => {
|
||||
const session = Session.create(SessionId('splice-inbox'))
|
||||
const inbox = new Inbox(session, { inserted: () => {}, discarded: () => {} })
|
||||
const inbox = new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} })
|
||||
const first = createUserMessage({
|
||||
content: [{ type: 'text', text: 'first' }],
|
||||
source: { kind: 'user' },
|
||||
@@ -113,6 +114,7 @@ describe('Inbox', () => {
|
||||
const session = Session.create(SessionId('clear-inbox'))
|
||||
const discarded: UserMessage[] = []
|
||||
const inbox = new Inbox(session, {
|
||||
claimed: () => {},
|
||||
inserted: () => {},
|
||||
discarded: message => void discarded.push(message),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user