fix pre-step cancellation and compaction convergence
This commit is contained in:
@@ -432,16 +432,24 @@ async function runTurn(ctx: Context, agent: ReactLoopAgent, handle: LoopHandle,
|
||||
// pre-step plugin ends the turn, not the loop.
|
||||
await ctx.serial('agent/pre-step', agent, turn, step, fullSystemPrompt, abort.signal)
|
||||
|
||||
// Interruption landing during the pre-step seam: do not open an empty
|
||||
// step. `agent/step-start` listeners get their own check below because
|
||||
// they necessarily run after step/start is appended/emitted.
|
||||
if (handle.isCancelled() || handle.isDisposed()) {
|
||||
handle.setAbort(undefined)
|
||||
reason = handle.isDisposed() ? { kind: 'disposed' } : { kind: 'aborted', reason: handle.cancelReason() }
|
||||
break
|
||||
}
|
||||
|
||||
session.append('step/start', { turn, step })
|
||||
stepOpen = true
|
||||
ctx.emit('agent/step-start', agent, turn, step)
|
||||
|
||||
// Cancel landing in the seam / step-start window: a `cancel()` during the
|
||||
// pre-step seam (it aborted `abort.signal` above) OR a synchronous
|
||||
// `agent/step-start` listener that cancels. And disposal, which the earlier
|
||||
// assembly check may have missed if it only checked isCancelled. Check
|
||||
// AFTER step/start append + emit and before `runStep`: drop the step, end
|
||||
// the turn accordingly. closeStep balances the already-appended step/start.
|
||||
// Cancel landing in the step-start window: a synchronous
|
||||
// `agent/step-start` listener can cancel after the step is already open.
|
||||
// Check AFTER step/start append + emit and before `runStep`: drop the
|
||||
// step, end the turn accordingly. closeStep balances the already-appended
|
||||
// step/start.
|
||||
if (handle.isCancelled() || handle.isDisposed()) {
|
||||
handle.setAbort(undefined)
|
||||
reason = handle.isDisposed() ? { kind: 'disposed' } : { kind: 'aborted', reason: handle.cancelReason() }
|
||||
|
||||
@@ -1216,6 +1216,7 @@ describe('disposal/cancel honored during pre-step assembly (P1-1)', () => {
|
||||
const turnEnd = e.findLast(x => x.type === 'turn/end')
|
||||
// Disposal wins the post-seam check — reason is `disposed`.
|
||||
expect(turnEnd?.type === 'turn/end' && turnEnd.data.reason).toEqual({ kind: 'disposed' })
|
||||
expect(e.some(x => x.type === 'step/start')).toBe(false)
|
||||
expect(e.some(x => x.type === 'assistant/chunk')).toBe(false)
|
||||
// agent/turn-end may not fire when disposal happens during pre-step: the
|
||||
// fiber's disposer runs before closeTurn(true)'s emit. The durable turn/end
|
||||
@@ -1265,6 +1266,7 @@ describe('disposal/cancel honored during pre-step assembly (P1-1)', () => {
|
||||
expect(e.filter(x => x.type === 'turn/end')).toHaveLength(1)
|
||||
const turnEnd = e.findLast(x => x.type === 'turn/end')
|
||||
expect(turnEnd?.type === 'turn/end' && turnEnd.data.reason).toEqual({ kind: 'aborted', reason: 'user cancelled' })
|
||||
expect(e.some(x => x.type === 'step/start')).toBe(false)
|
||||
expect(e.some(x => x.type === 'assistant/chunk')).toBe(false)
|
||||
expect(reasons).toEqual([{ kind: 'aborted', reason: 'user cancelled' }])
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user