fix(agent-loop): suppress cancel-requested for a no-op keepInbox cancel

cancel() emitted agent/cancel-requested whenever queued or steering work
existed, even under keepInbox with no active turn — a call the contract
documents as a no-op. Consumers could misread that notification as a real
cancellation. Emit only when the call actually aborts the active turn or
discards pending work, matching the "effective call" contract.
This commit is contained in:
_Kerman
2026-07-27 01:22:19 +08:00
parent 9ef55cda98
commit 9676696a31
2 changed files with 11 additions and 2 deletions

View File

@@ -104,12 +104,17 @@ describe('Agent.cancel()', () => {
const agent = ctx.agentLoop.create(SessionId('a1'), { provider: 'mock', model: 'mock' })
const discards: unknown[] = []
ctx.on('agent/inbox/discard', (subject, items) => { if (subject === agent) discards.push(items) })
const cancelRequests: unknown[] = []
ctx.on('agent/cancel-requested', (subject, cause) => { if (subject === agent) cancelRequests.push(cause) })
// Queue a turn WITHOUT waking the driver, so it sits in the inbox.
agent.send({ content: [{ type: 'text', text: 'preserved' }], source: { kind: 'user' } }, { target: 'next-turn', wakeup: false })
// keepInbox cancel: no active turn, work preserved, no discard event.
// keepInbox cancel: no active turn, work preserved, no discard event. With
// nothing to abort and nothing discarded, the call is a documented no-op,
// so it emits no cancel-requested either.
agent.cancel({ kind: 'user' }, { keepInbox: true })
expect(discards).toEqual([])
expect(cancelRequests).toEqual([])
// The preserved item still runs once the driver is woken by a later send.
send(agent, 'wake it')