Files
orca/tests/e2e/ssh-orcad-workspace-tools.spec.ts
T
OrcaWinandOrcaWin 7d6603aa17 [comment audit] Complete repository-wide comment cleanup (#33249)
* [comment audit] Align legacy failure-read counts with cached display

* [comment audit] Condense shared provider lifecycle and browser recovery comments [skip ci]

* [comment audit] Condense shared generation, path and preview comments [skip ci]

* [comment audit] Condense host, file and Git contract rationale [skip ci]

* [comment audit] Tighten Git lifecycle and provider contract comments [skip ci]

* [comment audit] Clarify shared host authority and launch isolation [skip ci]

* [comment audit] simplify local access mobile and native chat contracts [skip ci]

* [comment audit] simplify native chat presentation and state contracts [skip ci]

* [comment audit] simplify native runtime and provider contracts [skip ci]

* [comment audit] simplify migration and orchestration contracts [skip ci]

* [comment audit] simplify plugin trust and process capture rationale [skip ci]

* [comment audit] simplify terminal delivery and remote compatibility rationale [skip ci]

* [comment audit] clarify remote recovery and launch wire rationale [skip ci]

* [comment audit] shorten shared RPC compatibility and runtime evidence comments [skip ci]

* [comment audit] simplify secure storage shell startup and sidebar rationale [skip ci]

* [comment audit] clarify sidebar, skill and SSH invariants [skip ci]

* [comment audit] condense structured chat delivery and timing rationale [skip ci]

* [comment audit] condense terminal replay and telemetry invariants [skip ci]

* [comment audit] clarify launch permission and terminal delivery rationale [skip ci]

* [comment audit] clarify workspace ownership and layout contracts [skip ci]

* [comment audit] wait for fitted and painted SSH terminal links

* [comment audit] clarify layout persistence and session ownership [skip ci]

* [comment audit] clarify host routing and workspace lifecycle contracts [skip ci]

* [comment audit] clarify startup persistence and app lifetime comments [skip ci]

* [comment audit] clarify recovery and activity boundaries [skip ci]

* [comment audit] condense activity and artifact invariants [skip ci]

* [comment audit] condense automation authority contracts [skip ci]

* [comment audit] simplify automation scheduling and history contracts [skip ci]

* [comment audit] clarify browser chrome and annotation lifecycles [skip ci]

* [comment audit] clarify browser routing and retained guest ownership [skip ci]

* [comment audit] clarify browser recovery and document ownership comments [skip ci]

* [comment audit] restore hosted runners required by native IME fixtures

* [comment audit] shorten preview, palette and dashboard invariants [skip ci]

* [comment audit] clarify terminal preview, dashboard and retention comments [skip ci]

* [comment audit] clarify diff-note recovery and editor lifecycle comments [skip ci]

* [comment audit] shorten rich-text and combined-diff ownership comments [skip ci]

* [comment audit] shorten autosave, draft recovery and model ownership comments [skip ci]

* [comment audit] shorten save-queue, notebook and Markdown constraints [skip ci]

* [comment audit] shorten Monaco, PDF and rich Markdown safeguards [skip ci]

* [comment audit] clarify rich Markdown source and selection safeguards [skip ci]

* [comment audit] clarify editor loading and emulator stream lifetimes [skip ci]

* [comment audit] trim emulator and feature tour narration [skip ci]

* [comment audit] clarify floating workspace and GitHub cache ownership [skip ci]

* [comment audit] trim provider controls and Linear filter explanations [skip ci]

* [comment audit] clarify pairing and chat resume lifetimes [skip ci]

* [comment audit] clarify chat delivery and transcript lifetimes [skip ci]

* [comment audit] clarify session options and host-owned status [skip ci]

* [comment audit] clarify draft durability and live-status precedence [skip ci]

* [comment audit] Trim chat send and model ownership explanations [skip ci]

* [comment audit] Shorten transcript ownership and send recovery comments [skip ci]

* [comment audit] Simplify native chat composer and transcript hook comments [skip ci]

* [comment audit] Shorten native chat scroll and structured session explanations [skip ci]

* [comment audit] Trim workspace picker and onboarding commentary [skip ci]

* [comment audit] Simplify pet rendering, review cache and quick open comments [skip ci]

* [comment audit] Trim sidebar and session history explanations [skip ci]

* [comment audit] Condense session resume and review checks comments [skip ci]

* [comment audit] Shorten checks state and file explorer refresh comments [skip ci]

* [comment audit] Trim plugin bridge and source-control action rationale [skip ci]

* [comment audit] Trim source-control listing and review rationale [skip ci]

* [comment audit] Shorten sidebar lifecycle and explorer safeguards [skip ci]

* [comment audit] Condense Settings account and appearance rationale [skip ci]

* [comment audit] Trim pairing and repository Settings explanations [skip ci]

* [comment audit] Condense Settings search SSH and terminal safeguards [skip ci]

* [comment audit] Shorten Settings voice and provider ownership comments [skip ci]

* [comment audit] Condense Settings draft persistence and host-state rationale [skip ci]

* [comment audit] Trim Settings hooks and sidebar setup rationale [skip ci]

* [comment audit] Condense sidebar deletion and board-filter safeguards [skip ci]

* [comment audit] Condense worktree cards, host ownership and Markdown contracts [skip ci]

* [comment audit] Condense sidebar drag, folder and filtering contracts [skip ci]

* [comment audit] Condense sidebar parity, sleep and host ownership contracts [skip ci]

* [comment audit] Prune focus-read baseline after comment cleanup

* [comment audit] Clarify sidebar visibility, board drops and agent evidence [skip ci]

* [comment audit] Tighten sidebar listing, drag and reveal contracts [skip ci]

* [comment audit] Clarify viewport geometry and skill update safeguards [skip ci]

* [comment audit] Tighten skill inventory, usage and host status comments [skip ci]

* [comment audit] Clarify usage ownership and resource kill safeguards [skip ci]

* [comment audit] Tighten tab selection, rename and search contracts [skip ci]

* [comment audit] Tighten tab-strip geometry and drag ownership comments [skip ci]

* [comment audit] Tighten Tasks recovery, pagination and mutation comments [skip ci]

* [comment audit] Tighten provider loading and terminal recovery comments [skip ci]

* [comment audit] Tighten completion, cadence and PTY ownership comments [skip ci]

* [comment audit] Clarify terminal restore and parked-pane boundaries [skip ci]

* [comment audit] Tighten PTY startup and replay explanations [skip ci]

* [comment audit] Clarify PTY identity and delivery ownership [skip ci]

* [comment audit] Shorten remote reconnect and input replay rationale [skip ci]

* [comment audit] Condense terminal parking clipboard and file link comments [skip ci]

* [comment audit] Clarify IME ownership and trim trace history [skip ci]

* [comment audit] Shorten pane recovery and parked watcher explanations [skip ci]

* [comment audit] Condense snapshot replay and visibility recovery rationale [skip ci]

* [comment audit] Shorten terminal parking and keyboard ownership rationale [skip ci]

* [comment audit] Trim renderer lifecycle and menu rationale [skip ci]

* [comment audit] Condense workspace cleanup ownership and settlement notes [skip ci]

* [comment audit] Clarify renderer hook evidence and source ownership [skip ci]

* [comment audit] Tighten IPC ownership and reconnect explanations [skip ci]

* [comment audit] Trim renderer focus, paste and attention comments [skip ci]

* [comment audit] Shorten scroll, locale and agent lifecycle comments [skip ci]

* [comment audit] Trim agent launch, resume and ownership commentary [skip ci]

* [comment audit] Shorten account, browser and editor comments [skip ci]

* [comment audit] Clarify host-mirror waits, previews and input ownership [skip ci]

* [comment audit] Condense launch recovery and editor tokenization comments [skip ci]

* [comment audit] Condense workspace selection and terminal pane rationale [skip ci]

* [comment audit] Condense terminal fitting and rendering recovery comments [skip ci]

* [comment audit] Condense terminal input and host routing explanations [skip ci]

* [comment audit] Condense ownership and sleeping-session recovery rationale [skip ci]

* [comment audit] Condense structured launch and terminal link explanations [skip ci]

* [comment audit] Condense terminal ownership and workspace file routing rationale [skip ci]

* [comment audit] Condense session persistence and workspace activation rationale [skip ci]

* [comment audit] Condense workspace routing and browser ownership rationale [skip ci]

* [comment audit] Condense mirror recovery and layout authority rationale [skip ci]

* [comment audit] Condense runtime transport and file safety rationale [skip ci]

* [comment audit] Condense runtime graph and structured session rationale [skip ci]

* [comment audit] Condense browser creation and remote tab recovery rationale [skip ci]

* [comment audit] Condense snapshot authority and publisher recovery rationale [skip ci]

* [comment audit] Condense frame, startup and provider cache rationale [skip ci]

* [comment audit] Condense host twin and agent lifecycle rationale [skip ci]

* [comment audit] Condense browser authority and editor cleanup rationale [skip ci]

* [comment audit] Condense editor buffer identity and review cache rationale [skip ci]

* [comment audit] Condense provider lifetime and repository ownership rationale [skip ci]

* [comment audit] Condense reconnect, hydration and terminal retirement rationale [skip ci]

* [comment audit] Condense recovery, reconciliation and persistence rationale [skip ci]

* [comment audit] Condense cleanup ownership and listing admission rationale [skip ci]

* [comment audit] Condense teardown ordering and terminal ownership rationale [skip ci]

* [comment audit] Condense recovery ledger and web host-boundary rationale [skip ci]

* [comment audit] Condense web reconnect and credential lifecycle rationale [skip ci]

* [comment audit] Condense cloud rollout and assignment safety rationale [skip ci]

* [comment audit] Condense relay session and database recovery rationale [skip ci]

* [comment audit] Condense relay placement and protocol compatibility rationale [skip ci]

* [comment audit] Condense build and packaging constraints [skip ci]

* [comment audit] Condense CI gates and validation isolation rationale [skip ci]

* [comment audit] Condense catalog identity and browser validation rationale [skip ci]

* [comment audit] Condense mobile browser and frame-budget rationale [skip ci]

* [comment audit] Shorten mobile route and browser test explanations [skip ci]

* [comment audit] Condense editor and session seam test rationale [skip ci]

* [comment audit] Trim session and terminal closure history [skip ci]

* [comment audit] Shorten terminal browser and census explanations [skip ci]

* [comment audit] Preserve root-only E2E diagnostic baseline

* [comment audit] Condense mobile seam and HTML preview test rationale [skip ci]

* [comment audit] Condense preview instrumentation and render harness rationale [skip ci]

* [comment audit] Condense bundle contracts and native-build constraints [skip ci]

* [comment audit] Shorten native packaging and CI routing rationale [skip ci]

* [comment audit] Condense release tooling and benchmark constraints [skip ci]

* [comment audit] Condense packaging and platform gate rationale [skip ci]

* [comment audit] Shorten mobile route and history rationale [skip ci]

* [comment audit] Condense mobile browser and component rationale [skip ci]

* [comment audit] Shorten drawer and workspace-form rationale [skip ci]

* [comment audit] Trim mobile review and editor-document explanations [skip ci]

* [comment audit] Condense editor lifecycle and Markdown explanations [skip ci]

* [comment audit] Trim mobile file, diagnostics and dictation commentary [skip ci]

* [comment audit] Simplify mobile host-list and workspace-view explanations [skip ci]

* [comment audit] Clarify mobile shell lifetime and bridge delivery constraints [skip ci]

* [comment audit] Condense bridge stream, native request and audio rationale [skip ci]

* [comment audit] Reduce bridge schema and page-client commentary [skip ci]

* [comment audit] Tighten mobile page bridge contracts [skip ci]

* [comment audit] Clarify mobile cache and session constraints [skip ci]

* [comment audit] Condense mobile route and lifecycle rationale [skip ci]

* [comment audit] Tighten mobile shell hook timing rationale [skip ci]

* [comment audit] Clarify route handoff and notification compatibility [skip ci]

* [comment audit] Clarify push registration and dictation cleanup constraints [skip ci]

* [comment audit] Condense keyboard geometry and native media ownership rationale [skip ci]

* [comment audit] Tighten mobile chat scroll and composer concurrency rationale [skip ci]

* [comment audit] Condense mobile reply compatibility and upload ownership rationale [skip ci]

* [comment audit] Condense optimistic echo and scroll ownership rationale [skip ci]

* [comment audit] Tighten stream replay and queued session ownership rationale [skip ci]

* [comment audit] Condense mobile composer and reply-schema constraints [skip ci]

* [comment audit] Clarify mobile transcript recovery and session ownership [skip ci]

* [comment audit] Clarify mobile queue and source-control compatibility [skip ci]

* [comment audit] Condense storage and task launch constraints [skip ci]

* [comment audit] Clarify task schemas and terminal document ownership [skip ci]

* [comment audit] Tighten terminal input and viewport rationale [skip ci]

* [comment audit] Shorten terminal and RPC recorder contracts [skip ci]

* [comment audit] Condense recorder engine and connection contracts [skip ci]

* [comment audit] Shorten host credential and relay recovery comments [skip ci]

* [comment audit] Tighten pairing recovery and bundle transport rationale [skip ci]

* [comment audit] Clarify RPC delivery and workspace catalog boundaries [skip ci]

* [comment audit] Condense catalog and relay entry contracts [skip ci]

* [comment audit] Shorten shutdown and SSH relay delivery contracts [skip ci]

* [comment audit] Condense WSL relay and E2E regression rationale [skip ci]

* [comment audit] Shorten cross-version harness and renderer fixture explanations [skip ci]

* [comment audit] Condense paired-host and E2E lifecycle rationale [skip ci]

* [comment audit] Shorten layout oracle and regression explanations [skip ci]

* [comment audit] Condense paired recovery and ownership regression contracts [skip ci]

* [comment audit] Shorten restart, sidebar and SSH lifecycle contracts [skip ci]

* [comment audit] Condense terminal rendering and IME regression rationale [skip ci]

* [comment audit] Shorten terminal recovery and benchmark contracts [skip ci]

* [comment audit] Condense tooling and Windows lifecycle proof rationale [skip ci]

* [comment audit] Condense workflow routing and release rationale [skip ci]

* [comment audit] Condense mobile security and installer contracts [skip ci]

* [comment audit] Shorten native rationale and stylesheet workarounds [skip ci]

* [comment audit] Shorten Windows visibility probe rationale [skip ci]

* [comment audit] Condense embedded release and signing rationale [skip ci]

* [comment audit] Retain current main test behavior in final reconciliation [skip ci]

* [comment audit] Complete final comment review across current source inventory

* [comment audit] Handle consolidated audit reports before final CI

* [comment audit] Preserve credential storage census detail [skip ci]

* [comment audit] Scale broad changed E2E selections with verified timing shards

* [comment audit] Align E2E assertions with browser close and settled session metadata

* [comment audit] Match existing E2Es to current resource and windowing contracts

* [comment audit] Observe the shared server outage notice in reconnect coverage

* [comment audit] Make existing renderer probes measure settled tab and PTY work

* [comment audit] Align palette E2E selector with task search

* [comment audit] Refresh remaining palette search selectors

* [comment audit] Keep selector correction within selected audit specs

* [comment audit] Await emoji fixture settings inside renderer evaluation

* [comment audit] Cancel fixture fit retries between terminal tests

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-25 04:05:46 -07:00

274 lines
13 KiB
TypeScript

// Exercise managed SSH Quick Open at monorepo scale, ports/forwards, outside-workspace image links and remote agent status.
import { expect, test } from './helpers/orca-app'
import { reconnect } from './helpers/orcad-convert-flow'
import { callEnvironment, createPairedHostTerminal } from './helpers/paired-host-terminal'
import { reserveLocalPort } from './helpers/reserve-local-port'
import { openTerminalWorkspaceRootLink } from './helpers/terminal-workspace-root-link'
import {
echoMarker,
expectTerminalAnswers,
activateManagedWorkspace,
launchManagedWorkspace,
openTerminalTab,
relaunchManagedApp,
skipUnlessManagedDockerHost,
typeInTerminal,
withManagedHost
} from './helpers/orcad-managed-workspace'
import { quotePosixShell } from '../../src/shared/shell-argument-quoting'
import { createServer } from 'node:net'
import { waitForSessionReady } from './helpers/store'
skipUnlessManagedDockerHost()
test.skip(process.platform === 'win32', 'The Docker SSH host uses POSIX tooling')
test('Quick Open finds a file in a monorepo-sized managed workspace', async (// oxlint-disable-next-line no-empty-pattern -- Owns its app launch through a restart session.
{}, testInfo) => {
test.setTimeout(10 * 60_000)
await withManagedHost(testInfo, async (fixture) => {
const repo = quotePosixShell(fixture.host.remoteRepoPath)
// ~22.6k tracked paths: past the size whose single reply once overflowed the transport (#12547).
fixture.host.exec!(
[
`cd ${repo}`,
'for d in $(seq 1 226); do mkdir -p packages/pkg-$d/src && (cd packages/pkg-$d/src && ' +
'touch $(seq -f "module-with-a-long-descriptive-name-%05g.ts" 1 100)); done',
'touch packages/pkg-200/src/quick-open-needle-target.ts',
'git add -A',
'git -c user.email=e2e@test.local -c user.name=e2e commit -q -m "monorepo-sized tree"'
].join(' && ')
)
const { page } = await launchManagedWorkspace(fixture)
await page.evaluate(() => window.__store?.getState().openModal('quick-open'))
const dialog = page.getByRole('dialog', { name: 'Go to file' })
await expect(dialog).toBeVisible()
const input = dialog.getByPlaceholder('Go to file...')
await input.fill('quick-open-needle-target')
await expect(
dialog.getByRole('option').filter({ hasText: 'quick-open-needle-target.ts' })
).toHaveCount(1, { timeout: 90_000 })
await input.fill('module-with-a-long-descriptive-name-00042')
await expect(dialog.getByRole('option').first()).toBeVisible({ timeout: 60_000 })
await expect(dialog.getByText('Loading files...')).toHaveCount(0)
await expect(dialog).not.toContainText('Remote Orca runtime closed the connection')
await page.screenshot({ path: testInfo.outputPath('managed-quick-open.png') })
})
})
for (const across of ['', 'reconnect', 'restart without an SSH connect'] as const) {
test(`a managed host reports its listener, and an SSH forward reaches it${across ? ` across a ${across}` : ''}`, async (// oxlint-disable-next-line no-empty-pattern -- Owns its app launch through a restart session.
{}, testInfo) => {
test.setTimeout(10 * 60_000)
const remotePort = 7860
const marker = `ORCA_FORWARD_${Date.now()}`
const reservation = await reserveLocalPort()
await withManagedHost(testInfo, async (fixture) => {
const launched = await launchManagedWorkspace(fixture)
const { seeded, environmentId } = launched
let page = launched.page
const script = `require('node:http').createServer((_q, r) => r.end(${JSON.stringify(marker)})).listen(${remotePort}, '127.0.0.1')`
// Started from the workspace so the scan attributes it to this workspace.
fixture.host.exec!(
`cd ${quotePosixShell(fixture.host.remoteRepoPath)} && nohup node -e ${quotePosixShell(script)} >/tmp/orca-http.log 2>&1 < /dev/null &`
)
// The scan the Ports panel shows for a workspace on this host.
await expect
.poll(
async () =>
JSON.stringify(await callEnvironment(page, environmentId, 'workspacePorts.scan', {})),
{ timeout: 60_000, message: 'the managed host never reported the listener' }
)
.toContain(`"port":${remotePort}`)
const localPort = reservation.port
await reservation.release()
const forward = await page.evaluate(
(args) => window.api.ssh.addPortForward({ ...args, remoteHost: '127.0.0.1' }),
{ targetId: seeded.targetId, localPort, remotePort, label: 'managed e2e' }
)
const fetchMarker = async (): Promise<string> => {
try {
return await (await fetch(`http://127.0.0.1:${localPort}/`)).text()
} catch (error) {
return String(error)
}
}
await expect.poll(fetchMarker, { timeout: 30_000 }).toBe(marker)
let forwardId = forward.id
if (across) {
if (across === 'reconnect') {
await reconnect(page, seeded.targetId)
} else {
// #32484: every SSH connect fails, so only the server's tunnel brings the host back.
page = await relaunchManagedApp(fixture, undefined, {
extraEnv: {
ORCA_E2E_FORBID_LOCAL_SSH_CONNECT_PROBE: testInfo.outputPath('ssh-connects.log')
}
})
await callEnvironment(page, environmentId, 'workspacePorts.scan', {})
}
await expect.poll(fetchMarker, { timeout: 60_000 }).toBe(marker)
// The restored forward is a new entry for the same saved port.
const restored = await page.evaluate(
(targetId) => window.api.ssh.listPortForwards({ targetId }),
seeded.targetId
)
forwardId = restored.find((entry) => entry.localPort === localPort)?.id ?? forwardId
}
await page.evaluate((id) => window.api.ssh.removePortForward({ id }), forwardId)
await expect.poll(fetchMarker, { timeout: 30_000 }).not.toBe(marker)
}).finally(() => reservation.release())
})
}
test('a saved forward whose local port is taken after a restart shows why, and can be removed', async (// oxlint-disable-next-line no-empty-pattern -- Owns its app launch through a restart session.
{}, testInfo) => {
// Deploys a managed server, then quits and relaunches the app once.
test.setTimeout(10 * 60_000)
const reservation = await reserveLocalPort()
const localPort = reservation.port
await reservation.release()
await withManagedHost(testInfo, async (fixture) => {
const launched = await launchManagedWorkspace(fixture)
const { seeded, environmentId } = launched
await launched.page.evaluate(
(args) => window.api.ssh.addPortForward({ ...args, remoteHost: '127.0.0.1' }),
{ targetId: seeded.targetId, localPort, remotePort: 7860, label: 'held' }
)
await fixture.session.close(launched.app)
fixture.app = null
// Another process takes the port while Orca is closed, so the restore can't bind it.
const holder = createServer()
await new Promise<void>((resolve) => holder.listen(localPort, '127.0.0.1', resolve))
try {
const { app, page } = await fixture.session.launch()
fixture.app = app
await waitForSessionReady(page)
await callEnvironment(page, environmentId, 'workspacePorts.scan', {})
await expect
.poll(
async () =>
JSON.stringify(
await page.evaluate(
(targetId) => window.api.ssh.listUnrestoredPortForwards({ targetId }),
seeded.targetId
)
),
{ timeout: 60_000, message: 'the held forward never reported why it did not start' }
)
.toContain('"error"')
await activateManagedWorkspace(page, seeded.worktreeId, environmentId)
await page.evaluate(() => {
const state = window.__store?.getState()
state?.setRightSidebarOpen(true)
state?.setRightSidebarTab('ports')
})
const row = page.locator('[data-ssh-unrestored-forward]')
await expect(row).toBeVisible({ timeout: 30_000 })
await expect(row).toContainText("Couldn't start")
await page.screenshot({ path: testInfo.outputPath('unrestored-forward-row.png') })
await row.getByRole('button').click()
await expect(row).toHaveCount(0)
await expect
.poll(() =>
page.evaluate(
(targetId) => window.api.ssh.listUnrestoredPortForwards({ targetId }),
seeded.targetId
)
)
.toEqual([])
} finally {
await new Promise<void>((resolve) => holder.close(() => resolve()))
}
})
})
test('a terminal link opens an image outside the managed workspace', async (// oxlint-disable-next-line no-empty-pattern -- Owns its app launch through a restart session.
{}, testInfo) => {
test.setTimeout(10 * 60_000)
const imagePath = '/tmp/orca-ssh-external-preview.png'
const imageBase64 =
'iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVR4AWN8z8DwnwEJMDGgAcICAO2mBAXmO4drAAAAAElFTkSuQmCC'
await withManagedHost(testInfo, async (fixture) => {
fixture.host.exec!(
`printf '%s' ${quotePosixShell(imageBase64)} | base64 -d > ${quotePosixShell(imagePath)}`
)
// Why /root: the default folder workspace is /tmp, which would make the image a workspace file.
const { page, seeded } = await launchManagedWorkspace(fixture, { folderPath: '/root' })
// A link is only clickable once the shell has printed it, so wait for one that answers.
await expectTerminalAnswers(page, await openTerminalTab(page, seeded.worktreeId))
await openTerminalWorkspaceRootLink(page, testInfo, imagePath, 'open')
const preview = page.locator(`img[alt="${imagePath.split('/').at(-1)}"]`)
await expect(preview).toBeVisible({ timeout: 30_000 })
expect(await preview.evaluate((element: HTMLImageElement) => element.naturalWidth)).toBe(2)
await expect(page.getByText('Unable to load file', { exact: true })).toHaveCount(0)
await page.screenshot({ path: testInfo.outputPath('managed-external-image.png') })
})
})
test('a managed terminal reports agent status from hooks and from Pi-style titles', async (// oxlint-disable-next-line no-empty-pattern -- Owns its app launch through a restart session.
{}, testInfo) => {
test.setTimeout(10 * 60_000)
await withManagedHost(testInfo, async (fixture) => {
const { page, seeded, environmentId } = await launchManagedWorkspace(fixture)
const tabId = await openTerminalTab(page, seeded.worktreeId)
await echoMarker(page, tabId, 'HOOK_READY')
// Backgrounded: a foreground post ends the shell command, which rightly clears the turn.
const prompt = `orca managed hook ${Date.now()}`
const payload = JSON.stringify({ hook_event_name: 'UserPromptSubmit', prompt })
await typeInTerminal(
page,
tabId,
[
'( sleep 0.2; curl -sS -X POST "http://127.0.0.1:${ORCA_AGENT_HOOK_PORT}/hook/codex"',
'-H "X-Orca-Agent-Hook-Token: ${ORCA_AGENT_HOOK_TOKEN}"',
'--data-urlencode "paneKey=${ORCA_PANE_KEY}" --data-urlencode "tabId=${ORCA_TAB_ID}"',
'--data-urlencode "worktreeId=${ORCA_WORKTREE_ID}" --data-urlencode "env=${ORCA_AGENT_HOOK_ENV}"',
'--data-urlencode "version=${ORCA_AGENT_HOOK_VERSION}"',
`--data-urlencode ${quotePosixShell(`payload=${payload}`)} >/tmp/orca-hook.log 2>&1 ) &`
].join(' ')
)
await expect
.poll(
() =>
page.evaluate(
(prompt) =>
Object.values(window.__store?.getState().agentStatusByPaneKey ?? {}).some(
(entry) =>
entry.prompt === prompt &&
entry.agentType === 'codex' &&
entry.state === 'working'
),
prompt
),
{ timeout: 30_000, message: 'the host hook never reached the agent status store' }
)
.toBe(true)
// The host classifies agent titles a remote terminal emits.
const terminal = await createPairedHostTerminal(page, environmentId, seeded.worktreeId, 'bash')
const status = async (): Promise<unknown> => {
const result = await callEnvironment(page, environmentId, 'terminal.agentStatus', {
terminal: terminal.terminal
})
return typeof result === 'object' && result !== null && 'agentStatus' in result
? result.agentStatus
: result
}
for (const [title, expected] of [
['⠋ OMP', 'working'],
['OMP ready', 'idle'],
['⠋ Pi', 'working']
] as const) {
await callEnvironment(page, environmentId, 'terminal.send', {
terminal: terminal.terminal,
text: `printf '\\033]0;${title}\\007'`,
enter: true
})
await expect
.poll(status, { timeout: 15_000, message: `title ${title}` })
.toMatchObject({ isRunningAgent: true, status: expected })
}
})
})