test(orchestration): stabilize agent prompt submit e2e

This commit is contained in:
Brennan Benson
2026-08-26 22:10:56 -07:00
parent dcb1d047cf
commit 9e2b773769
2 changed files with 151 additions and 63 deletions
@@ -1,46 +1,88 @@
import { execFile } from 'node:child_process'
import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { chmodSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { promisify } from 'node:util'
import { expect, test } from './helpers/orca-app'
import { buildFakeAgentCommandOverride } from './helpers/fake-agent-command-override'
import { waitForSessionReady } from './helpers/store'
import { RuntimeClient } from '../../src/cli/runtime-client'
import { recognizeAgentProcess } from '../../src/shared/agent-process-recognition'
import { SWALLOWED_ENTER_FIXTURE_TIMEOUT_MS } from '../../src/shared/orchestration-timing-budgets'
const execFileAsync = promisify(execFile)
const fixtureRoot = mkdtempSync(path.join(os.tmpdir(), 'orca-terminal-send-agent-prompt-'))
const fixtureBin = path.join(fixtureRoot, 'bin')
const fixtureReport = path.join(fixtureRoot, 'report.json')
const fixtureMarker = `ORCA_TERMINAL_SEND_E2E_${process.pid}`
const fixtureScript = path.join(process.cwd(), 'tests', 'tools', 'repro-terminal-send-submit.mjs')
const fakeCodex = path.join(fixtureBin, process.platform === 'win32' ? 'codex.cmd' : 'codex')
const fakeCodex = path.join(fixtureRoot, process.platform === 'win32' ? 'codex.cmd' : 'codex')
const fakeCodexCommand = buildFakeAgentCommandOverride(fakeCodex)
const swallowedEnterFixtureTimeoutMs = SWALLOWED_ENTER_FIXTURE_TIMEOUT_MS
mkdirSync(fixtureBin)
writeFileSync(
fakeCodex,
process.platform === 'win32'
? `@echo off\r\n"${process.execPath}" "${fixtureScript}" --fake-agent --report "%ORCA_FAKE_AGENT_REPORT%" --marker "%ORCA_FAKE_AGENT_MARKER%" --allow-unframed-paste %*\r\n`
: `#!/usr/bin/env sh\nexec "${process.execPath}" "${fixtureScript}" --fake-agent --report "$ORCA_FAKE_AGENT_REPORT" --marker "$ORCA_FAKE_AGENT_MARKER" "$@"\n`,
: `#!/usr/bin/env sh\n"${process.execPath}" "${fixtureScript}" --fake-agent --report "$ORCA_FAKE_AGENT_REPORT" --marker "$ORCA_FAKE_AGENT_MARKER" "$@"\n`,
'utf8'
)
if (process.platform !== 'win32') {
chmodSync(fakeCodex, 0o755)
}
test.use({
seedTestRepo: false,
orcaAppExtraEnv: {
PATH: `${fixtureBin}${path.delimiter}${process.env.PATH ?? ''}`,
ORCA_FAKE_AGENT_REPORT: fixtureReport,
ORCA_FAKE_AGENT_MARKER: fixtureMarker
}
})
test.afterAll(() => {
rmSync(fixtureRoot, { recursive: true, force: true })
})
async function createFakeCodexTerminal(
userDataDir: string,
testRepoPath: string,
args: string[] = []
): Promise<string> {
const client = new RuntimeClient(userDataDir, 30_000, null, null)
const expectedPath = path.resolve(testRepoPath)
const findWorktree = async (): Promise<{ id: string } | undefined> => {
const listed = await client.call<{ worktrees: { id: string; path: string }[] }>(
'worktree.list',
{}
)
return listed.result.worktrees.find((worktree) => {
const candidatePath = path.resolve(worktree.path)
return process.platform === 'win32'
? candidatePath.toLowerCase() === expectedPath.toLowerCase()
: candidatePath === expectedPath
})
}
await expect.poll(async () => Boolean(await findWorktree()), { timeout: 60_000 }).toBe(true)
const worktree = await findWorktree()
if (!worktree) {
throw new Error(`runtime did not register ${testRepoPath}`)
}
const created = await client.call<{ terminal: { handle: string } }>('terminal.create', {
worktree: `id:${worktree.id}`,
command: [fakeCodexCommand, ...args].join(' '),
launchAgent: 'codex',
env: {
ORCA_FAKE_AGENT_REPORT: fixtureReport,
ORCA_FAKE_AGENT_MARKER: fixtureMarker
},
title: 'terminal send submit repro'
})
const handle = created.result.terminal.handle
await expect
.poll(
async () => {
const inspected = await client.call<{
process: { foregroundProcess: string | null }
}>('terminal.inspectProcess', { terminal: handle })
return recognizeAgentProcess(inspected.result.process.foregroundProcess)?.agent ?? null
},
{ timeout: 30_000 }
)
.toBe('codex')
return handle
}
test('CLI text plus Enter waits for a slow agent composer before submitting', async ({
electronApp,
orcaPage,
@@ -49,6 +91,7 @@ test('CLI text plus Enter waits for a slow agent composer before submitting', as
test.setTimeout(110_000)
await waitForSessionReady(orcaPage)
const userDataDir = await electronApp.evaluate(({ app }) => app.getPath('userData'))
const terminal = await createFakeCodexTerminal(userDataDir, testRepoPath)
const repoRoot = process.cwd()
let stdout = ''
try {
@@ -60,8 +103,8 @@ test('CLI text plus Enter waits for a slow agent composer before submitting', as
path.join(repoRoot, 'config', 'scripts', 'orca-dev.mjs'),
'--worktree',
testRepoPath,
'--agent-command',
'codex',
'--terminal',
terminal,
'--report',
fixtureReport,
'--marker',
@@ -99,6 +142,11 @@ test('CLI reports a swallowed Enter without submitting a second Enter', async ({
test.setTimeout(110_000)
await waitForSessionReady(orcaPage)
const userDataDir = await electronApp.evaluate(({ app }) => app.getPath('userData'))
const terminal = await createFakeCodexTerminal(userDataDir, testRepoPath, [
'--swallow-first-enter',
'--timeout-ms',
String(swallowedEnterFixtureTimeoutMs)
])
const repoRoot = process.cwd()
let stdout = ''
try {
@@ -110,8 +158,10 @@ test('CLI reports a swallowed Enter without submitting a second Enter', async ({
path.join(repoRoot, 'config', 'scripts', 'orca-dev.mjs'),
'--worktree',
testRepoPath,
'--agent-command',
`codex --swallow-first-enter --timeout-ms ${swallowedEnterFixtureTimeoutMs}`,
'--terminal',
terminal,
'--timeout-ms',
String(swallowedEnterFixtureTimeoutMs),
'--expect-stalled',
'--report',
fixtureReport,
@@ -152,6 +202,9 @@ test('CLI does not write prompt bytes into an active permission dialog', async (
test.setTimeout(110_000)
await waitForSessionReady(orcaPage)
const userDataDir = await electronApp.evaluate(({ app }) => app.getPath('userData'))
const terminal = await createFakeCodexTerminal(userDataDir, testRepoPath, [
'--permission-before-send'
])
const repoRoot = process.cwd()
let stdout = ''
try {
@@ -163,6 +216,8 @@ test('CLI does not write prompt bytes into an active permission dialog', async (
path.join(repoRoot, 'config', 'scripts', 'orca-dev.mjs'),
'--worktree',
testRepoPath,
'--terminal',
terminal,
'--expect-blocked',
'--report',
fixtureReport,
+78 -45
View File
@@ -1,8 +1,9 @@
#!/usr/bin/env node
import { spawn } from 'node:child_process'
import { mkdir, readFile, rm, symlink, writeFile } from 'node:fs/promises'
import { chmod, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import path from 'node:path'
import { pathToFileURL } from 'node:url'
const SCRIPT_PATH = import.meta.filename
const BEGIN = '\x1b[200~'
@@ -118,6 +119,20 @@ async function closeTerminal(cli, handle, cwd) {
}
}
async function waitForPermissionPrompt(cli, handle, cwd) {
const deadline = Date.now() + 10_000
let lastTerminal = null
while (Date.now() < deadline) {
const shown = await callOrca(cli, ['terminal', 'show', '--terminal', handle], cwd)
lastTerminal = shown.terminal ?? null
if (lastTerminal?.agentWait) {
return
}
await new Promise((resolve) => setTimeout(resolve, 100))
}
throw new Error(`terminal permission prompt was not observed: ${JSON.stringify(lastTerminal)}`)
}
async function waitForWorktreeSelector(cli, repoId, cwd) {
const deadline = Date.now() + 10_000
const expectedPath = path.resolve(cwd)
@@ -138,18 +153,25 @@ async function waitForWorktreeSelector(cli, repoId, cwd) {
}
async function createFakeCodexCommand(tempDir, args) {
const launcherPath = path.join(tempDir, 'codex')
const runnerPath = path.join(tempDir, 'fake-agent.mjs')
await writeFile(runnerPath, `import ${JSON.stringify(pathToFileURL(SCRIPT_PATH).href)}\n`, 'utf8')
if (process.platform === 'win32') {
const launcherPath = path.join(tempDir, 'codex.cmd')
const commandPath = path.join(tempDir, 'codex.cmd')
await writeFile(
launcherPath,
`@echo off\r\n"${process.execPath}" "${SCRIPT_PATH}" %*\r\n`,
commandPath,
`@echo off\r\n"${process.execPath}" "%~dp0fake-agent.mjs" %*\r\n`,
'utf8'
)
return [shellQuote(launcherPath), ...args].join(' ')
return [shellQuote(commandPath), ...args].join(' ')
}
const launcherPath = path.join(tempDir, 'codex')
await symlink(process.execPath, launcherPath)
return [shellQuote(launcherPath), shellQuote(SCRIPT_PATH), ...args].join(' ')
await writeFile(
launcherPath,
`#!/usr/bin/env sh\n"${process.execPath}" "${runnerPath}" "$@"\n`,
'utf8'
)
await chmod(launcherPath, 0o755)
return [shellQuote(launcherPath), ...args].join(' ')
}
async function parentMain() {
@@ -162,43 +184,48 @@ async function parentMain() {
const prompt = `${marker} ${'slow composer payload '.repeat(24)}`
const expectStalled = hasFlag('expect-stalled')
const expectBlocked = hasFlag('expect-blocked')
const providedHandle = argValue('terminal')
await mkdir(tempDir, { recursive: true })
await rm(reportPath, { force: true })
const command =
argValue('agent-command') ??
(await createFakeCodexCommand(tempDir, [
'--fake-agent',
'--report',
shellQuote(reportPath),
'--marker',
shellQuote(marker),
'--timeout-ms',
String(timeoutMs),
...(expectBlocked ? ['--permission-before-send'] : []),
...(process.platform === 'win32' ? ['--allow-unframed-paste'] : [])
]))
const added = await callOrca(cli, ['repo', 'add', '--path', cwd], cwd)
const repoId = added.repo?.id
if (!repoId) {
throw new Error('repo add returned no id')
let handle = providedHandle
if (!handle) {
const command =
argValue('agent-command') ??
(await createFakeCodexCommand(tempDir, [
'--fake-agent',
'--report',
shellQuote(reportPath),
'--marker',
shellQuote(marker),
'--timeout-ms',
String(timeoutMs),
...(expectStalled ? ['--swallow-first-enter'] : []),
...(expectBlocked ? ['--permission-before-send'] : []),
...(process.platform === 'win32' ? ['--allow-unframed-paste'] : [])
]))
const added = await callOrca(cli, ['repo', 'add', '--path', cwd], cwd)
const repoId = added.repo?.id
if (!repoId) {
throw new Error('repo add returned no id')
}
const worktreeSelector = await waitForWorktreeSelector(cli, repoId, cwd)
const created = await callOrca(
cli,
[
'terminal',
'create',
'--worktree',
worktreeSelector,
'--title',
'terminal send submit repro',
'--command',
command
],
cwd
)
handle = created.terminal?.handle
}
const worktreeSelector = await waitForWorktreeSelector(cli, repoId, cwd)
const created = await callOrca(
cli,
[
'terminal',
'create',
'--worktree',
worktreeSelector,
'--title',
'terminal send submit repro',
'--command',
command
],
cwd
)
const handle = created.terminal?.handle
if (!handle) {
throw new Error('terminal create returned no handle')
}
@@ -209,6 +236,7 @@ async function parentMain() {
if (!setupReport) {
throw new Error('terminal permission prompt did not materialize')
}
await waitForPermissionPrompt(cli, handle, cwd)
} else {
await callOrca(
cli,
@@ -278,6 +306,7 @@ async function parentMain() {
}
async function fakeAgentMain() {
process.title = 'codex'
const reportPath = argValue('report')
const marker = argValue('marker')
const timeoutMs = parsePositiveInteger('timeout-ms', DEFAULT_TIMEOUT_MS)
@@ -291,10 +320,14 @@ async function fakeAgentMain() {
process.stdin.setRawMode(true)
}
process.stdin.resume()
process.stdout.write('OpenAI Codex\nmodel: fake\ndirectory: fixture\n> ')
if (permissionBeforeSend) {
process.stdout.write('\nPermission required\nAllow once\nAllow always\nReject\n')
}
process.stdout.write('\x1b]0;Codex working\x07')
setTimeout(() => {
const title = permissionBeforeSend ? 'Codex permission' : 'Codex Ready'
process.stdout.write(`\x1b]0;${title}\x07OpenAI Codex\nmodel: fake\ndirectory: fixture\n> `)
if (permissionBeforeSend) {
process.stdout.write('\nPermission required\nAllow once\nAllow always\nReject\n')
}
}, 100)
let input = ''
let countedCarriages = 0