Files
orca/tests/e2e/codex-ctrl-c-status.spec.ts
T
Neil bfd1e9c579 fix(codex): keep working status when Escape closes search or permissions (#25769)
* fix(codex): preserve working status when Escape dismisses a view

* test(codex): cover navigation Escape during terminal exit cleanup
2026-10-05 21:34:27 -07:00

147 lines
5.8 KiB
TypeScript

import { appendFileSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import {
buildShellCommandFromArgv,
resolveStartupShell
} from '../../src/shared/tui-agent-startup-shell'
import { resolveLocalWindowsAgentStartupShell } from '../../src/shared/windows-terminal-shell'
import { test, expect } from './helpers/orca-app'
import { emitCodexHookStatus, readHookEndpoint } from './helpers/agent-hook-endpoint'
import { ensureTerminalVisible, waitForActiveWorktree, waitForSessionReady } from './helpers/store'
import {
focusActiveTerminalInput,
execInTerminal,
waitForActivePanePtyId,
waitForTerminalOutput,
waitForActivePaneHookDescriptor,
waitForActiveTerminalManager
} from './helpers/terminal'
// Replay navigation input through a real PTY; hook and rollout fixtures own agent status.
for (const navigation of ['Control+c', 'F3 search', '/permissions'] as const) {
test(`Codex ${navigation} preserves working status until a confirmed interruption`, async ({
orcaPage,
electronApp
}, testInfo) => {
await waitForSessionReady(orcaPage)
await waitForActiveWorktree(orcaPage)
await ensureTerminalVisible(orcaPage)
await waitForActiveTerminalManager(orcaPage, 30_000)
const endpoint = await readHookEndpoint(electronApp)
const descriptor = await waitForActivePaneHookDescriptor(orcaPage)
await orcaPage.evaluate(() => {
const state = window.__store?.getState()
state?.setAgentActivityDisplayMode('full')
if (state && !state.worktreeCardProperties.includes('inline-agents')) {
state.setWorktreeCardProperties([...state.worktreeCardProperties, 'inline-agents'])
}
})
const dir = mkdtempSync(join(tmpdir(), 'orca-codex-interruption-'))
const transcriptPath = join(dir, 'rollout-root.jsonl')
writeFileSync(
transcriptPath,
`${JSON.stringify({ type: 'event_msg', payload: { type: 'task_started', turn_id: 'turn-1' } })}\n`
)
try {
const inputScript = join(dir, 'codex-input.cjs')
writeFileSync(
inputScript,
`
process.stdin.setRawMode(true)
process.stdin.resume()
process.stdin.on('data', (chunk) => {
if (chunk.includes(3)) process.stdout.write('ORCA_CTRL_C_RECEIVED\\n')
if (chunk.toString() === '\\x1bOR' || chunk.toString() === '\\x1b[13~') {
process.stdout.write('ORCA_F3_SEARCH_OPENED\\n')
}
if (chunk.includes(13)) process.stdout.write('ORCA_PERMISSIONS_OPENED\\n')
if (chunk.toString() === '\\x1b') process.stdout.write('ORCA_ESCAPE_RECEIVED: view dismissed; main task continues\\n')
})
process.stdout.write('ORCA_CODEX_INPUT_READY\\n')
`
)
const terminalWindowsShell = await orcaPage.evaluate(
() => window.__store?.getState().settings.terminalWindowsShell
)
const shell = resolveStartupShell(
process.platform,
resolveLocalWindowsAgentStartupShell({
platform: process.platform,
isRemote: false,
terminalWindowsShell
})
)
const ptyId = await waitForActivePanePtyId(orcaPage)
await execInTerminal(
orcaPage,
ptyId,
buildShellCommandFromArgv([process.execPath, inputScript], shell)
)
await waitForTerminalOutput(orcaPage, 'ORCA_CODEX_INPUT_READY')
await emitCodexHookStatus(endpoint, {
...descriptor,
transcriptPath,
sessionId: 'main-session',
state: 'working',
prompt: 'Main task continues'
})
const working = orcaPage.locator('[aria-label="Working"]')
const interrupted = orcaPage.locator('[aria-label="Interrupted"]')
await expect(working.first()).toBeVisible()
await emitCodexHookStatus(endpoint, {
...descriptor,
state: 'working',
sessionId: 'side-session',
transcriptPath: null,
prompt: 'Side chat'
})
await expect(orcaPage.getByText('Main task continues', { exact: true })).toBeVisible()
await focusActiveTerminalInput(orcaPage)
const key = navigation === 'Control+c' ? 'Control+c' : 'Escape'
if (navigation === 'F3 search') {
await orcaPage.keyboard.press('F3')
await waitForTerminalOutput(orcaPage, 'ORCA_F3_SEARCH_OPENED')
} else if (navigation === '/permissions') {
await orcaPage.keyboard.type('/permissions')
await orcaPage.keyboard.press('Enter')
await waitForTerminalOutput(orcaPage, 'ORCA_PERMISSIONS_OPENED')
}
await orcaPage.keyboard.press(key)
await waitForTerminalOutput(
orcaPage,
key === 'Escape' ? 'ORCA_ESCAPE_RECEIVED' : 'ORCA_CTRL_C_RECEIVED'
)
// Allow the old 500 ms inference timer to fire before recording the rendered result.
await orcaPage.waitForTimeout(1_000)
await orcaPage.screenshot({
path: testInfo.outputPath('status-after-input.png')
})
await expect(interrupted).toHaveCount(0)
await expect(working.first()).toBeVisible()
appendFileSync(
transcriptPath,
`${JSON.stringify({ type: 'event_msg', payload: { type: 'turn_aborted', turn_id: 'turn-1', reason: 'interrupted' } })}\n`
)
await expect(interrupted.first()).toBeVisible()
await expect(working).toHaveCount(0)
await orcaPage.screenshot({
path: testInfo.outputPath('status-after-confirmed-interruption.png')
})
await emitCodexHookStatus(endpoint, {
...descriptor,
state: 'working',
prompt: 'Next main task'
})
await expect(working.first()).toBeVisible()
await expect(interrupted).toHaveCount(0)
await emitCodexHookStatus(endpoint, { ...descriptor, state: 'done' })
await expect(working).toHaveCount(0)
await expect(interrupted).toHaveCount(0)
} finally {
rmSync(dir, { recursive: true, force: true })
}
})
}