fix(antigravity): do not infer interruption from navigation Escape (#21636)

This commit is contained in:
Neil
2026-09-20 21:03:05 -07:00
committed by GitHub
parent 618571d1db
commit e0ac49ee49
3 changed files with 63 additions and 27 deletions
@@ -157,6 +157,7 @@ describe('navigation Escape during an open tool call', () => {
})
it.each([
['antigravity', 'PreToolUse'],
['pi', 'tool_call'],
['prime-agent', 'tool_execution_start']
])('leaves %s work running when Escape closes an overlay mid-%s', (agentType, hookEventName) => {
@@ -175,6 +176,37 @@ describe('navigation Escape during an open tool call', () => {
expect(server.getStatusSnapshotForPane(PANE)[0]).toMatchObject({ state: 'working' })
})
it('keeps Antigravity working through repeated navigation Escape until its Stop event', () => {
const server = new AgentHookServer()
const turn = { source: 'antigravity', prompt: 'inspect files', agentType: 'antigravity' }
ingest(server, { ...turn, hookEventName: 'PreInvocation', state: 'working' })
const published = collectPublishedStates(server)
vi.setSystemTime(1_200)
expect(pressInterruptKey(server, 'plain-escape', 2)).toBe(false)
expect(server.getStatusSnapshotForPane(PANE)[0]).toMatchObject({ state: 'working' })
expect(published).toEqual([])
vi.setSystemTime(1_500)
ingest(server, { ...turn, hookEventName: 'Stop', state: 'done' })
expect(server.getStatusSnapshotForPane(PANE)[0]).toMatchObject({ state: 'done' })
})
it('still accepts Ctrl+C for Antigravity work', () => {
const server = new AgentHookServer()
ingest(server, {
source: 'antigravity',
hookEventName: 'PreInvocation',
state: 'working',
prompt: 'inspect files',
agentType: 'antigravity'
})
vi.setSystemTime(1_200)
expect(pressInterruptKey(server, 'ctrl-c')).toBe(true)
expect(server.getStatusSnapshotForPane(PANE)[0]).toMatchObject({
state: 'done',
interrupted: true
})
})
it('leaves OMP work running when Escape lands between approval and execution (#9208)', () => {
const server = new AgentHookServer()
ingest(server, {
@@ -348,7 +348,7 @@ describe('agent interrupt inference', () => {
entry = undefined
})
it.each([['claude'], ['omp'], ['pi'], ['prime-agent']] as const)(
it.each([['claude'], ['omp'], ['pi'], ['prime-agent'], ['antigravity']] as const)(
'never asks main to interrupt %s on a single Escape while working',
(agentType) => {
// Why: Escape is ambiguous at the source for these TUIs, so the renderer does not spend a
@@ -372,33 +372,36 @@ describe('agent interrupt inference', () => {
}
)
it.each([['claude'], ['omp']] as const)('still forwards Ctrl+C for %s', (agentType) => {
vi.useFakeTimers()
let entry: AgentStatusEntry | undefined = makeEntry({ agentType, toolName: 'Bash' })
const inferInterrupt = vi.fn().mockReturnValue(true)
const tracker = createAgentInterruptInference({
paneKey: PANE_KEY,
getStatusEntry: () => entry,
inferInterrupt,
now: () => 1_100
})
it.each([['claude'], ['omp'], ['antigravity']] as const)(
'still forwards Ctrl+C for %s',
(agentType) => {
vi.useFakeTimers()
let entry: AgentStatusEntry | undefined = makeEntry({ agentType, toolName: 'Bash' })
const inferInterrupt = vi.fn().mockReturnValue(true)
const tracker = createAgentInterruptInference({
paneKey: PANE_KEY,
getStatusEntry: () => entry,
inferInterrupt,
now: () => 1_100
})
tracker.observeInputIntent('ctrl-c')
vi.advanceTimersByTime(500)
tracker.observeInputIntent('ctrl-c')
vi.advanceTimersByTime(500)
expect(inferInterrupt).toHaveBeenCalledWith({
paneKey: PANE_KEY,
baselineUpdatedAt: 1_000,
baselineStateStartedAt: 900,
baselinePrompt: 'write tests',
baselineAgentType: agentType,
intent: 'ctrl-c'
})
tracker.dispose()
entry = undefined
})
expect(inferInterrupt).toHaveBeenCalledWith({
paneKey: PANE_KEY,
baselineUpdatedAt: 1_000,
baselineStateStartedAt: 900,
baselinePrompt: 'write tests',
baselineAgentType: agentType,
intent: 'ctrl-c'
})
tracker.dispose()
entry = undefined
}
)
it.each([['claude'], ['omp'], ['pi'], ['prime-agent']] as const)(
it.each([['claude'], ['omp'], ['pi'], ['prime-agent'], ['antigravity']] as const)(
'keeps a pending Ctrl+C for %s when a navigation Escape lands before it settles',
(agentType) => {
// Why: Escape is not a retraction. The user asked to interrupt; dismissing an overlay
+3 -2
View File
@@ -18,11 +18,12 @@ export function isAgentInterruptInputIntent(intent: unknown): intent is AgentInt
return intent === 'plain-escape' || intent === 'ctrl-c'
}
// Why: these TUIs also close an overlay on a bare Escape (Claude's /btw composer, OMP/Pi's
// focused-child and settings views). The keypress is ambiguous at the source and nothing outside
// Why: these TUIs also close an overlay on a bare Escape (Antigravity's /usage, Claude's /btw,
// OMP/Pi's focused-child and settings views). The keypress is ambiguous at the source and nothing outside
// the TUI can disambiguate it, so it is never evidence a turn ended — only the provider's own
// hook may retire the row (#13547, #9208). Ctrl+C is unaffected; it has no navigation meaning.
const ESCAPE_ALSO_NAVIGATES_AGENT_TYPES: ReadonlySet<AgentType> = new Set([
'antigravity',
'claude',
'omp',
'pi',