mirror of
https://github.com/stablyai/orca.git
synced 2026-10-14 00:02:42 +00:00
Ignore Droid Ctrl-C for interrupt inference
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -317,6 +317,45 @@ describe('AgentHookServer listener replay', () => {
|
||||
}
|
||||
)
|
||||
|
||||
it('rejects Ctrl+C inference for Droid', () => {
|
||||
vi.useFakeTimers()
|
||||
vi.setSystemTime(1_000)
|
||||
try {
|
||||
const server = new AgentHookServer()
|
||||
server.ingestRemote(
|
||||
{
|
||||
paneKey: PANE,
|
||||
tabId: 'tab-1',
|
||||
worktreeId: 'wt-1',
|
||||
payload: { state: 'working', prompt: 'long task', agentType: 'droid' }
|
||||
},
|
||||
'conn-1'
|
||||
)
|
||||
const baseline = server.getStatusSnapshot()[0]
|
||||
|
||||
vi.setSystemTime(1_500)
|
||||
const applied = server.inferInterrupt({
|
||||
paneKey: PANE,
|
||||
baselineUpdatedAt: baseline.receivedAt,
|
||||
baselineStateStartedAt: baseline.stateStartedAt,
|
||||
baselinePrompt: 'long task',
|
||||
baselineAgentType: 'droid',
|
||||
intent: 'ctrl-c'
|
||||
})
|
||||
|
||||
expect(applied).toBe(false)
|
||||
expect(server.getStatusSnapshot()).toEqual([
|
||||
expect.objectContaining({
|
||||
state: 'working',
|
||||
prompt: 'long task',
|
||||
agentType: 'droid'
|
||||
})
|
||||
])
|
||||
} finally {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
})
|
||||
|
||||
it('does not let late same-turn working hooks resurrect an inferred interrupt', () => {
|
||||
vi.useFakeTimers()
|
||||
vi.setSystemTime(1_000)
|
||||
|
||||
@@ -311,6 +311,11 @@ export class AgentHookServer {
|
||||
}
|
||||
const payload = existing.payload
|
||||
const agentType: AgentType | undefined = payload.agentType
|
||||
// Why: Droid's Ctrl+C does not interrupt the current turn; repeated Ctrl+C
|
||||
// exits the CLI, which is handled by process/PTY lifecycle cleanup.
|
||||
if (agentType === 'droid' && request.intent === 'ctrl-c') {
|
||||
return false
|
||||
}
|
||||
// Why: these agents use the first Escape as a TUI/editor cancel. A single
|
||||
// Escape can leave the turn running, so only a deliberate double Escape
|
||||
// may infer an interrupted turn.
|
||||
|
||||
@@ -128,6 +128,25 @@ describe('agent interrupt inference', () => {
|
||||
entry = undefined
|
||||
})
|
||||
|
||||
it('does not infer Ctrl+C for Droid', () => {
|
||||
vi.useFakeTimers()
|
||||
let entry: AgentStatusEntry | undefined = makeEntry({ agentType: 'droid' })
|
||||
const inferInterrupt = vi.fn()
|
||||
const tracker = createAgentInterruptInference({
|
||||
paneKey: PANE_KEY,
|
||||
getStatusEntry: () => entry,
|
||||
inferInterrupt,
|
||||
now: () => 1_100
|
||||
})
|
||||
|
||||
tracker.observeInputIntent('ctrl-c')
|
||||
vi.advanceTimersByTime(500)
|
||||
|
||||
expect(inferInterrupt).not.toHaveBeenCalled()
|
||||
tracker.dispose()
|
||||
entry = undefined
|
||||
})
|
||||
|
||||
it.each(['opencode', 'copilot'] as const)(
|
||||
'infers immediately on double Escape for %s',
|
||||
(agentType) => {
|
||||
|
||||
@@ -49,6 +49,13 @@ function shouldFlushInterruptImmediately(
|
||||
)
|
||||
}
|
||||
|
||||
function shouldIgnoreInterruptIntent(
|
||||
agentType: AgentStatusEntry['agentType'],
|
||||
intent: AgentInterruptInputIntent
|
||||
): boolean {
|
||||
return agentType === 'droid' && intent === 'ctrl-c'
|
||||
}
|
||||
|
||||
function isSameTurnBaseline(
|
||||
left: CapturedInterruptBaseline,
|
||||
right: CapturedInterruptBaseline
|
||||
@@ -192,6 +199,10 @@ export function createAgentInterruptInference({
|
||||
clearPending()
|
||||
return
|
||||
}
|
||||
if (shouldIgnoreInterruptIntent(baseline.agentType, intent)) {
|
||||
clearPending()
|
||||
return
|
||||
}
|
||||
if (requiresDoubleEscapeForAgent(baseline.agentType, intent)) {
|
||||
const isSecondEscape =
|
||||
doubleEscapeBaseline !== null && isSameTurnBaseline(doubleEscapeBaseline, baseline)
|
||||
|
||||
Reference in New Issue
Block a user