orca/tests/e2e/ssh-codex-reconnect-replay-...

236 lines
7.6 KiB
TypeScript

import { execFileSync } from 'node:child_process'
import type { Page } from '@stablyai/playwright-test'
import { expect } from './helpers/orca-app'
import {
DOCKER_SSH_RELAY_REMOTE_REPO_PATH,
type DockerSshRelayTarget
} from './helpers/docker-ssh-relay-target'
export type ConnectedDockerRemote = {
targetId: string
worktreeId: string
}
export function dropDockerSshClientSessions(target: DockerSshRelayTarget): void {
execFileSync(
'docker',
[
'exec',
target.containerName,
'bash',
'-lc',
`ps -eo pid=,comm=,args= | awk '$2 == "sshd" && index($0, "sshd: root") { print $1 }' | xargs -r kill -9`
],
{ stdio: ['ignore', 'pipe', 'pipe'], timeout: 60_000 }
)
}
export async function connectDockerRemote(
page: Page,
target: DockerSshRelayTarget
): Promise<ConnectedDockerRemote> {
return await page.evaluate(
async ({ target, remotePath }) => {
const store = window.__store
if (!store) {
throw new Error('Store unavailable')
}
const credentialUnsub = window.api.ssh.onCredentialRequest((request) => {
void window.api.ssh.submitCredential({ requestId: request.requestId, value: null })
})
try {
const { target: createdTarget, repoReadoptions } = await window.api.ssh.addTarget({
target: {
label: `Docker SSH Codex Artifact Repro ${Date.now()}`,
host: '127.0.0.1',
port: target.port,
username: 'root',
identityFile: target.identityFile,
identitiesOnly: true,
relayGracePeriodSeconds: 1
}
})
store.getState().recordSshRepoReadoptions(repoReadoptions)
const state = await window.api.ssh.connect({ targetId: createdTarget.id })
if (!state || state.status !== 'connected') {
throw new Error(`SSH target did not connect: ${JSON.stringify(state)}`)
}
store.getState().setSshConnectionState(createdTarget.id, state)
const labels = new Map(store.getState().sshTargetLabels)
labels.set(createdTarget.id, createdTarget.label)
store.getState().setSshTargetLabels(labels)
const result = await window.api.repos.addRemote({
connectionId: createdTarget.id,
remotePath,
displayName: 'Docker SSH Codex Artifact Repro'
})
if ('error' in result) {
throw new Error(result.error)
}
await store.getState().fetchRepos()
await store.getState().fetchWorktrees(result.repo.id)
const worktree = (store.getState().worktreesByRepo[result.repo.id] ?? [])[0]
if (!worktree) {
throw new Error(`No remote worktree found for ${result.repo.path}`)
}
store.getState().setActiveWorktree(worktree.id)
if ((store.getState().tabsByWorktree[worktree.id] ?? []).length === 0) {
store.getState().createTab(worktree.id)
}
store.getState().setActiveTabType('terminal')
return { targetId: createdTarget.id, worktreeId: worktree.id }
} finally {
credentialUnsub()
}
},
{ target, remotePath: DOCKER_SSH_RELAY_REMOTE_REPO_PATH }
)
}
export async function switchToNonRemoteWorktree(
page: Page,
remoteWorktreeId: string
): Promise<string> {
const otherWorktreeId = await page.evaluate((remoteWorktreeId) => {
const store = window.__store
if (!store) {
return null
}
const state = store.getState()
const other = Object.values(state.worktreesByRepo)
.flat()
.find((worktree) => worktree.id !== remoteWorktreeId)
if (!other) {
return null
}
state.setActiveWorktree(other.id)
return other.id
}, remoteWorktreeId)
if (!otherWorktreeId) {
throw new Error('No non-remote worktree available to hide the SSH terminal')
}
return otherWorktreeId
}
export async function installPtyReplayProbe(page: Page): Promise<void> {
await page.evaluate(() => {
const api = window.api?.pty
if (!api || typeof api.onReplay !== 'function') {
throw new Error('PTY replay API unavailable')
}
const holder = window as unknown as {
__orcaSshCodexReplayProbe?: {
payloads: { id: string; length: number; preview: string }[]
dispose: () => void
}
}
holder.__orcaSshCodexReplayProbe?.dispose()
const payloads: { id: string; length: number; preview: string }[] = []
const dispose = api.onReplay(({ id, data }) => {
payloads.push({
id,
length: data.length,
preview: data.slice(-400)
})
})
holder.__orcaSshCodexReplayProbe = { payloads, dispose }
})
}
export async function waitForDockerRemoteReconnected(page: Page, targetId: string): Promise<void> {
let observedNonConnected = false
await expect
.poll(
async () => {
const status = await page.evaluate((targetId) => {
const state = window.__store?.getState().sshConnectionStates.get(targetId)
return state?.status ?? null
}, targetId)
if (status !== 'connected') {
observedNonConnected = true
}
return observedNonConnected && status === 'connected'
},
{
timeout: 90_000,
message: 'Docker SSH target did not auto-reconnect after transport drop'
}
)
.toBe(true)
}
export async function readReplayProbeSnapshot(page: Page): Promise<Record<string, unknown>> {
return page.evaluate(() => {
const probe = (
window as unknown as {
__orcaSshCodexReplayProbe?: {
payloads: { id: string; length: number; preview: string }[]
}
}
).__orcaSshCodexReplayProbe
return {
replayCount: probe?.payloads.length ?? 0,
replayPayloads: probe?.payloads.slice(-8) ?? []
}
})
}
export async function readDuplicateStatusRows(page: Page): Promise<string[]> {
return page.evaluate(() => {
const state = window.__store?.getState()
const worktreeId = state?.activeWorktreeId
const tabId =
state?.activeTabType === 'terminal'
? state.activeTabId
: worktreeId
? (state?.activeTabIdByWorktree?.[worktreeId] ?? null)
: null
const manager = tabId ? window.__paneManagers?.get(tabId) : null
const pane = manager?.getActivePane?.() ?? manager?.getPanes?.()[0] ?? null
const text = pane?.serializeAddon?.serialize?.() ?? ''
const counts = new Map<string, number>()
const escapeSequencePattern = new RegExp(
`${String.fromCharCode(27)}\\[[0-9;?]*[ -/]*[@-~]`,
'g'
)
for (const line of text.split(/\r?\n/)) {
const normalized = line.replace(escapeSequencePattern, '').trim()
if (!/gpt-5\.5|background terminal|\/ps to view|\/stop to close/i.test(normalized)) {
continue
}
counts.set(normalized, (counts.get(normalized) ?? 0) + 1)
}
return Array.from(counts)
.filter(([, count]) => count > 1)
.map(([line, count]) => `${count}x ${line}`)
.slice(0, 12)
})
}
export async function enableRiskyTerminalRendererPath(page: Page): Promise<void> {
await page.evaluate(() => {
const store = window.__store
if (!store) {
throw new Error('window.__store unavailable')
}
const state = store.getState()
store.setState({
settings: {
...state.settings!,
terminalGpuAcceleration: 'on',
theme: 'dark'
}
})
const worktreeId = state.activeWorktreeId
const tabId =
state.activeTabType === 'terminal'
? state.activeTabId
: worktreeId
? (state.activeTabIdByWorktree?.[worktreeId] ?? null)
: null
const manager = tabId ? window.__paneManagers?.get(tabId) : null
manager?.setTerminalGpuAcceleration('on')
})
}