ソースを参照

review fix: cover post-cancel replacement

pku-xht 3 ヶ月 前
親
コミット
7d7d69e225

+ 1 - 1
packages/core/agent-loop/src/loop.ts

@@ -133,7 +133,7 @@ export async function runLoop(ctx: Context, handle: LoopHandle): Promise<void> {
     if (handle.isDisposed()) break
 
     // Cancellation between wake and `running` skips only the cancelled work;
-    // a replacement prompt still runs and owns the eventual idle transition.
+    // a replacement prompt still runs before the eventual idle transition.
     if (handle.isCancelled()) {
       handle.clearCancel()
       if (!handle.inbox.hasQueued) {

+ 25 - 0
packages/core/agent-loop/tests/cancel.spec.ts

@@ -270,6 +270,31 @@ describe('Agent.cancel()', () => {
     expect(userTexts(agent)).toEqual(['first', 'later'])
   })
 
+  it('replacement work queued after idle-listener cancellation still runs', async () => {
+    const adapter = new MockAdapter([textResponse('first reply'), textResponse('replacement reply')])
+    const ctx = await harness(adapter)
+    const agent = ctx.agentLoop.create(SessionId('idle-listener-post-cancel-send'), { provider: 'mock', model: 'mock' })
+
+    const replacementRegistered = Promise.withResolvers<undefined>()
+    let replacementIdle: Promise<void> | undefined
+    ctx.on('agent/status', (subject, status) => {
+      if (subject !== agent || status !== 'idle' || replacementIdle !== undefined) return
+      send(agent, 'cancelled replacement')
+      agent.cancel('idle listener')
+      send(agent, 'surviving replacement')
+      replacementIdle = agent.whenIdle()
+      replacementRegistered.resolve(undefined)
+    })
+
+    send(agent, 'first')
+    await replacementRegistered.promise
+    if (replacementIdle === undefined) throw new Error('idle listener did not register replacement work')
+    await replacementIdle
+
+    expect(adapter.requests).toHaveLength(2)
+    expect(userTexts(agent)).toEqual(['first', 'surviving replacement'])
+  })
+
   it('cancel() mid-step aborts the active turn and drops every queued tail item', async () => {
     const adapter = new MockAdapter(['hang'])
     const ctx = await harness(adapter)