mirror of
https://github.com/stablyai/orca.git
synced 2026-10-03 00:02:19 +00:00
test: retire a dormant worker through host-owned status (#23686)
* test(e2e): await renderer recovery after worker exit * test(e2e): publish worker recovery through authenticated hooks * test: keep retired background worker dormant before activation
This commit is contained in:
@@ -160,21 +160,13 @@ for (const closeMode of ['terminal-close-cli', 'worker-release'] as const) {
|
||||
const workerBefore = terminalIdentity(worker)
|
||||
const workerPaneKey = `${worker.tabId}:${worker.leafId}`
|
||||
expect(worker.worktreeId).toBe(targetWorktreeId)
|
||||
await orcaPage.evaluate(
|
||||
({ tabId, worktreeId }) => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('orca-background-mount-terminal-worktree', {
|
||||
detail: { worktreeId, tabIds: [tabId] }
|
||||
})
|
||||
)
|
||||
},
|
||||
{ tabId: worker.tabId, worktreeId: targetWorktreeId }
|
||||
)
|
||||
await expect
|
||||
.poll(() =>
|
||||
orcaPage.evaluate((tabId) => Boolean(window.__paneManagers?.get(tabId)), workerBefore.tabId)
|
||||
// Keep the never-opened worker dormant so foreground-shell cleanup cannot retire it first.
|
||||
expect(
|
||||
await orcaPage.evaluate(
|
||||
(tabId) => Boolean(window.__paneManagers?.get(tabId)),
|
||||
workerBefore.tabId
|
||||
)
|
||||
.toBe(true)
|
||||
).toBe(false)
|
||||
expect(
|
||||
await orcaPage.evaluate(
|
||||
(worktreeId) => window.__store?.getState().everActivatedWorktreeIds.has(worktreeId),
|
||||
@@ -202,70 +194,13 @@ for (const closeMode of ['terminal-close-cli', 'worker-release'] as const) {
|
||||
throw new Error('Background worker did not receive its dispatch capability')
|
||||
}
|
||||
|
||||
const transcriptPath = seedCurrentCodexTranscript(
|
||||
isolatedHome,
|
||||
PROVIDER_SESSION_ID,
|
||||
targetWorktreePath
|
||||
)
|
||||
seedCurrentCodexTranscript(isolatedHome, PROVIDER_SESSION_ID, targetWorktreePath)
|
||||
|
||||
await orcaPage.evaluate(
|
||||
({
|
||||
agentCommand,
|
||||
paneKey,
|
||||
providerSessionId,
|
||||
tabId,
|
||||
terminalHandle,
|
||||
transcriptPath,
|
||||
worktreeId
|
||||
}) => {
|
||||
const state = window.__store?.getState()
|
||||
if (!state) {
|
||||
throw new Error('Renderer store unavailable')
|
||||
}
|
||||
const providerSession = {
|
||||
key: 'session_id' as const,
|
||||
id: providerSessionId,
|
||||
transcriptPath
|
||||
}
|
||||
const metadata = { tabId, worktreeId, terminalHandle }
|
||||
const recovery = {
|
||||
providerSession,
|
||||
launchConfig: {
|
||||
// Why not bare 'codex': resume prefers the captured command over
|
||||
// agentCmdOverrides, so a bare name would resolve the machine's real
|
||||
// Codex off PATH and unpin the adoption leg this spec exercises.
|
||||
agentCommand,
|
||||
agentArgs: '--dangerously-bypass-approvals-and-sandbox',
|
||||
agentEnv: {}
|
||||
}
|
||||
}
|
||||
state.setAgentStatus(
|
||||
paneKey,
|
||||
{ state: 'working', prompt: 'Report completion, then exit normally', agentType: 'codex' },
|
||||
'Completed background worker',
|
||||
undefined,
|
||||
metadata,
|
||||
recovery
|
||||
)
|
||||
state.setAgentStatus(
|
||||
paneKey,
|
||||
{ state: 'done', prompt: 'Report completion, then exit normally', agentType: 'codex' },
|
||||
'Completed background worker',
|
||||
undefined,
|
||||
metadata,
|
||||
recovery
|
||||
)
|
||||
},
|
||||
{
|
||||
agentCommand: completedWorkerFakeCodexCommand,
|
||||
paneKey: workerPaneKey,
|
||||
providerSessionId: PROVIDER_SESSION_ID,
|
||||
tabId: worker.tabId,
|
||||
terminalHandle: workerHandle,
|
||||
transcriptPath,
|
||||
worktreeId: targetWorktreeId
|
||||
}
|
||||
)
|
||||
await client.call('terminal.send', {
|
||||
terminal: workerHandle,
|
||||
text: 'ORCA_E2E_PUBLISH_DONE',
|
||||
enter: true
|
||||
})
|
||||
|
||||
const expectedRecovery = {
|
||||
origin: 'live',
|
||||
@@ -348,18 +283,23 @@ for (const closeMode of ['terminal-close-cli', 'worker-release'] as const) {
|
||||
await expect
|
||||
.poll(() => readCompletedWorkerLedger().filter((event) => event.event === 'normal-exit'))
|
||||
.toHaveLength(1)
|
||||
expect(
|
||||
await orcaPage.evaluate(
|
||||
({ paneKey, tabId, worktreeId }) => {
|
||||
const state = window.__store?.getState()
|
||||
return {
|
||||
tabPresent: Boolean(state?.tabsByWorktree[worktreeId]?.some((tab) => tab.id === tabId)),
|
||||
recoveryPresent: Boolean(state?.sleepingAgentSessionsByPaneKey[paneKey])
|
||||
}
|
||||
},
|
||||
{ paneKey: workerPaneKey, tabId: workerBefore.tabId, worktreeId: targetWorktreeId }
|
||||
// The exit marker precedes the asynchronous renderer recovery update.
|
||||
await expect
|
||||
.poll(() =>
|
||||
orcaPage.evaluate(
|
||||
({ paneKey, tabId, worktreeId }) => {
|
||||
const state = window.__store?.getState()
|
||||
return {
|
||||
tabPresent: Boolean(
|
||||
state?.tabsByWorktree[worktreeId]?.some((tab) => tab.id === tabId)
|
||||
),
|
||||
recoveryPresent: Boolean(state?.sleepingAgentSessionsByPaneKey[paneKey])
|
||||
}
|
||||
},
|
||||
{ paneKey: workerPaneKey, tabId: workerBefore.tabId, worktreeId: targetWorktreeId }
|
||||
)
|
||||
)
|
||||
).toEqual({ tabPresent: true, recoveryPresent: true })
|
||||
.toEqual({ tabPresent: true, recoveryPresent: true })
|
||||
|
||||
await orcaPage.evaluate(
|
||||
({ paneKey, tabId, worktreeId }) => {
|
||||
|
||||
@@ -20,14 +20,34 @@ import { buildFakeAgentCommandOverride } from './fake-agent-command-override'
|
||||
import { FAKE_AGENT_PASTE_END_SCANNER_SOURCE } from './fake-agent-paste-end-scanner'
|
||||
|
||||
const fakeCliDir = mkdtempSync(path.join(os.tmpdir(), 'orca-e2e-retired-worker-'))
|
||||
const recoveryConfigPath = path.join(fakeCliDir, 'recovery-config.json')
|
||||
const lifecycleLedgerPath = path.join(fakeCliDir, 'codex-lifecycle.jsonl')
|
||||
export const completedWorkerFakeCodexCommand = buildFakeAgentCommandOverride(
|
||||
path.join(fakeCliDir, process.platform === 'win32' ? 'codex.cmd' : 'codex')
|
||||
)
|
||||
const fakeCodexSource = `
|
||||
const { appendFileSync } = require('node:fs')
|
||||
const { appendFileSync, readFileSync } = require('node:fs')
|
||||
const ledger = process.env.ORCA_E2E_CODEX_LIFECYCLE_LEDGER
|
||||
const append = (event) => appendFileSync(ledger, JSON.stringify({ pid: process.pid, ...event }) + '\\n')
|
||||
async function publishRecovery() {
|
||||
const config = JSON.parse(readFileSync(${JSON.stringify(recoveryConfigPath)}, 'utf8'))
|
||||
for (const hook_event_name of ['UserPromptSubmit', 'Stop']) {
|
||||
const response = await fetch('http://127.0.0.1:' + process.env.ORCA_AGENT_HOOK_PORT + '/hook/codex', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-Orca-Agent-Hook-Token': process.env.ORCA_AGENT_HOOK_TOKEN },
|
||||
body: JSON.stringify({
|
||||
paneKey: process.env.ORCA_PANE_KEY,
|
||||
tabId: process.env.ORCA_TAB_ID,
|
||||
worktreeId: process.env.ORCA_WORKTREE_ID,
|
||||
launchToken: process.env.ORCA_AGENT_LAUNCH_TOKEN,
|
||||
env: process.env.ORCA_AGENT_HOOK_ENV,
|
||||
version: process.env.ORCA_AGENT_HOOK_VERSION,
|
||||
payload: { hook_event_name, prompt: 'Report completion, then exit normally', ...config }
|
||||
})
|
||||
})
|
||||
if (response.status !== 204) throw new Error('Recovery hook rejected: ' + response.status)
|
||||
}
|
||||
}
|
||||
const args = process.argv.slice(2)
|
||||
if (args.includes('app-server')) {
|
||||
process.stderr.write("error: unrecognized subcommand 'app-server'\\n")
|
||||
@@ -44,6 +64,10 @@ process.stdin.on('data', (chunk) => {
|
||||
process.stdout.write('\\x1b[?25h')
|
||||
}
|
||||
append({ event: 'input', input })
|
||||
if (input.includes('ORCA_E2E_PUBLISH_DONE')) {
|
||||
void publishRecovery().catch((error) => process.stderr.write(String(error)))
|
||||
return
|
||||
}
|
||||
if (input.includes('ORCA_E2E_EXIT_AFTER_DONE')) {
|
||||
append({ event: 'normal-exit' })
|
||||
process.exit(0)
|
||||
@@ -179,6 +203,10 @@ export function seedCurrentCodexTranscript(
|
||||
payload: { id: providerSessionId, cwd }
|
||||
})}\n`
|
||||
)
|
||||
writeFileSync(
|
||||
recoveryConfigPath,
|
||||
JSON.stringify({ session_id: providerSessionId, transcript_path: transcriptPath })
|
||||
)
|
||||
return transcriptPath
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user