mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
fix(acp): report end_turn for non-client turn aborts
TurnEndReasonMap aborted endings no longer map to 'cancelled': the ACP spec reserves StopReason::Cancelled for the client's own session/cancel notification (plus disposal, both settled out of band). A turn aborted by a hook or another agent owner is ordinary quiescence and now settles as end_turn, matching the README/note contract. interrupted keeps 'cancelled' as the crash-orphan marker.
This commit is contained in:
@@ -17,7 +17,11 @@ export function turnEndToStopReason(reason: TurnEndReason): StopReason {
|
||||
return 'end_turn'
|
||||
case 'max-tokens':
|
||||
return 'max_tokens'
|
||||
// `cancelled` is reserved for explicit client cancellation (`session/cancel`)
|
||||
// and disposal, both settled out of band; a turn aborted by a hook or
|
||||
// another owner is ordinary quiescence and reports `end_turn`.
|
||||
case 'aborted':
|
||||
return 'end_turn'
|
||||
case 'interrupted':
|
||||
return 'cancelled'
|
||||
case 'blocked':
|
||||
|
||||
@@ -6,7 +6,7 @@ describe('ACP codec', () => {
|
||||
it.each([
|
||||
[{ kind: 'completed' }, 'end_turn'],
|
||||
[{ kind: 'max-tokens' }, 'max_tokens'],
|
||||
[{ kind: 'aborted', reason: { kind: 'user' } }, 'cancelled'],
|
||||
[{ kind: 'aborted', reason: { kind: 'user' } }, 'end_turn'],
|
||||
[{ kind: 'interrupted' }, 'cancelled'],
|
||||
[{ kind: 'blocked' }, 'end_turn'],
|
||||
[{ kind: 'error', error: { message: 'failed', code: 'UNKNOWN' } }, 'end_turn'],
|
||||
|
||||
@@ -166,6 +166,18 @@ describe('ACP prompt lifecycle', () => {
|
||||
.toEqual({ kind: 'aborted', reason: { kind: 'user' } })
|
||||
})
|
||||
|
||||
it('settles a hook-cancelled turn as end_turn, not cancelled', async () => {
|
||||
harness = await makeBridgeHarness({ script: ['hang'] })
|
||||
const sessionId = await newSession(harness)
|
||||
const prompt = harness.client.prompt({ sessionId, prompt: [{ type: 'text', text: 'go' }] })
|
||||
const agent = harness.ctx.agents.get(SessionId(sessionId))!
|
||||
await vi.waitFor(() => { expect(agent.status).toBe('running') })
|
||||
// A hook or another owner cancels the agent: the ACP client never called
|
||||
// session/cancel, so this is ordinary quiescence and reports end_turn.
|
||||
agent.cancel({ kind: 'hook', reason: 'owner intervention' })
|
||||
await expect(prompt).resolves.toEqual({ stopReason: 'end_turn' })
|
||||
})
|
||||
|
||||
it('cancels autonomous running work without an in-flight prompt', async () => {
|
||||
harness = await makeBridgeHarness({ script: ['hang'] })
|
||||
const sessionId = await newSession(harness)
|
||||
|
||||
Reference in New Issue
Block a user