mirror of
https://github.com/stablyai/orca.git
synced 2026-10-18 16:03:17 +00:00
* perf(terminal): defer cold worktree activation tab mounts until first reveal Activating a worktree mounted a TerminalPane for every saved terminal tab in one render pass. Each mount replays scrollback through xterm, attaches a WebGL renderer, and issues a sync-IPC snapshot read, so a worktree with many agent-session tabs froze the renderer for tens of seconds (field trace: 200+ replay-guard stall releases inside one activation window, plus restore-marker feedback loops re-fetching snapshots at ~4/sec). Cold activations now mount only the tabs the user can see (active tab, each split group's active tab, activity-portal tabs, pending spawns) plus tabs that are already live or that parked byte watchers cannot cover. Every other tab defers like a cold-parked tab from birth: no view until first reveal, with the parked byte watchers owning bell/title/completion side effects meanwhile. The restriction reuses the targeted background mount mechanism and lifts once every tab has been revealed. Deferral only engages when more than four tabs would mount cold, and only while hidden-view parking is enabled, so small worktrees and the parking kill switch keep today's behavior. * test(terminal): prove cold-activation deferral end-to-end; unblock it past hydration's blanket spawn flag The new e2e (child worktree, 8 tabs, renderer reload against live daemon sessions) caught the deferral never engaging: session hydration marks every persisted tab pendingActivationSpawn, and treating that flag as must-mount-now put all tabs in the immediate set. Only explicit queued startups (pendingStartupByTabId) mount eagerly; a deferred tab's reveal consumes the hydration flag exactly like an activation mount would. Sleep/wake respawn flows go through targeted background mounts and are unaffected (terminal-sleep-wake-restore passes). The spec asserts the visible tab mounts, deferred tabs stay unmounted with parked byte-watcher coverage, and a revealed tab mounts on demand. * fix(terminal): harden deferred activation lifecycle * docs(reliability): correct SSH startup assertion evidence
204 lines
7.5 KiB
TypeScript
204 lines
7.5 KiB
TypeScript
import type { Page } from '@stablyai/playwright-test'
|
|
import { test, expect } from './helpers/orca-app'
|
|
import { waitForActiveWorktree, waitForSessionReady } from './helpers/store'
|
|
import {
|
|
focusActiveTerminalInput,
|
|
waitForActivePanePtyId,
|
|
waitForActiveTerminalManager
|
|
} from './helpers/terminal'
|
|
import {
|
|
cleanupDockerSshRelayTarget,
|
|
DOCKER_SSH_RELAY_REMOTE_REPO_PATH,
|
|
execDockerSshRelayTargetCommand,
|
|
startDockerSshRelayTarget,
|
|
type DockerSshRelayTarget
|
|
} from './helpers/docker-ssh-relay-target'
|
|
import { connectDockerSshRelayTarget } from './helpers/docker-ssh-relay-connection'
|
|
|
|
const RUN_DOCKER_SSH = process.env.ORCA_E2E_SSH_DOCKER === '1'
|
|
const TAB_COUNT = 6
|
|
|
|
test.use({ seedTestRepo: false })
|
|
|
|
async function createRemoteTerminalTab(page: Page, worktreeId: string): Promise<void> {
|
|
const tabId = await page.evaluate((id) => {
|
|
const state = window.__store?.getState()
|
|
if (!state) {
|
|
throw new Error('Store unavailable')
|
|
}
|
|
const tab = state.createTab(id, undefined, undefined, { activate: true })
|
|
state.setActiveTab(tab.id)
|
|
state.setActiveTabType('terminal')
|
|
return tab.id
|
|
}, worktreeId)
|
|
await expect
|
|
.poll(() => page.evaluate(() => window.__store?.getState().activeTabId ?? null), {
|
|
timeout: 10_000
|
|
})
|
|
.toBe(tabId)
|
|
await waitForActiveTerminalManager(page, 60_000)
|
|
await waitForActivePanePtyId(page, 60_000)
|
|
}
|
|
|
|
async function readRemoteTerminalTabs(
|
|
page: Page,
|
|
worktreeId: string
|
|
): Promise<{ id: string; ptyId: string | null }[]> {
|
|
return page.evaluate(
|
|
(id) =>
|
|
(window.__store?.getState().tabsByWorktree[id] ?? []).map((tab) => ({
|
|
id: tab.id,
|
|
ptyId: tab.ptyId
|
|
})),
|
|
worktreeId
|
|
)
|
|
}
|
|
|
|
test.describe('SSH cold activation restore', () => {
|
|
test.skip(!RUN_DOCKER_SSH, 'Set ORCA_E2E_SSH_DOCKER=1 to run Docker-backed SSH tests.')
|
|
test.skip(process.platform === 'win32', 'Docker SSH restore uses POSIX SSH tooling.')
|
|
|
|
test('eagerly remounts every restored remote terminal after renderer reload', async ({
|
|
orcaPage
|
|
}, testInfo) => {
|
|
test.setTimeout(240_000)
|
|
let target: DockerSshRelayTarget | null = null
|
|
try {
|
|
target = startDockerSshRelayTarget(testInfo)
|
|
await waitForSessionReady(orcaPage)
|
|
const remote = await connectDockerSshRelayTarget(orcaPage, target)
|
|
await expect
|
|
.poll(() => waitForActiveWorktree(orcaPage), { timeout: 30_000 })
|
|
.toBe(remote.worktreeId)
|
|
await waitForActiveTerminalManager(orcaPage, 60_000)
|
|
await waitForActivePanePtyId(orcaPage, 60_000)
|
|
|
|
while ((await readRemoteTerminalTabs(orcaPage, remote.worktreeId)).length < TAB_COUNT) {
|
|
await createRemoteTerminalTab(orcaPage, remote.worktreeId)
|
|
}
|
|
const beforeReload = await readRemoteTerminalTabs(orcaPage, remote.worktreeId)
|
|
expect(beforeReload).toHaveLength(TAB_COUNT)
|
|
expect(new Set(beforeReload.map((tab) => tab.ptyId)).size).toBe(TAB_COUNT)
|
|
expect(beforeReload.every((tab) => tab.ptyId !== null)).toBe(true)
|
|
|
|
await expect
|
|
.poll(
|
|
() =>
|
|
orcaPage.evaluate(
|
|
async ({ targetId, worktreePath }) => {
|
|
const snapshot = await window.api.remoteWorkspace.get({ targetId })
|
|
return (
|
|
snapshot?.session.tabsByWorktreePath[worktreePath]?.map((tab) => tab.id) ?? []
|
|
)
|
|
},
|
|
{
|
|
targetId: remote.targetId,
|
|
worktreePath: DOCKER_SSH_RELAY_REMOTE_REPO_PATH
|
|
}
|
|
),
|
|
{ timeout: 30_000, message: 'SSH tabs were not committed to the relay workspace' }
|
|
)
|
|
.toEqual(beforeReload.map((tab) => tab.id))
|
|
|
|
await orcaPage.evaluate(() => window.dispatchEvent(new Event('beforeunload')))
|
|
await expect
|
|
.poll(
|
|
() =>
|
|
orcaPage.evaluate(
|
|
async ({ targetId, worktreeId, expectedTabIds }) => {
|
|
const session = await window.api.session.get()
|
|
const persistedTabIds = new Set(
|
|
(session.tabsByWorktree[worktreeId] ?? []).map((tab) => tab.id)
|
|
)
|
|
return (
|
|
session.activeConnectionIdsAtShutdown?.includes(targetId) === true &&
|
|
expectedTabIds.every((tabId) => persistedTabIds.has(tabId))
|
|
)
|
|
},
|
|
{
|
|
targetId: remote.targetId,
|
|
worktreeId: remote.worktreeId,
|
|
expectedTabIds: beforeReload.map((tab) => tab.id)
|
|
}
|
|
),
|
|
{ timeout: 10_000, message: 'SSH tabs and active target were not persisted' }
|
|
)
|
|
.toBe(true)
|
|
|
|
await orcaPage.reload()
|
|
await waitForSessionReady(orcaPage, 60_000)
|
|
await expect
|
|
.poll(() => waitForActiveWorktree(orcaPage), { timeout: 60_000 })
|
|
.toBe(remote.worktreeId)
|
|
await expect
|
|
.poll(
|
|
() =>
|
|
orcaPage.evaluate(
|
|
(targetId) => window.__store?.getState().sshConnectionStates.get(targetId)?.status,
|
|
remote.targetId
|
|
),
|
|
{ timeout: 60_000, message: 'renderer SSH state did not restore' }
|
|
)
|
|
.toBe('connected')
|
|
|
|
const expectedTabIds = beforeReload.map((tab) => tab.id).sort()
|
|
await expect
|
|
.poll(
|
|
() =>
|
|
orcaPage.evaluate(
|
|
(ids) => ids.filter((tabId) => window.__paneManagers?.has(tabId)).sort(),
|
|
expectedTabIds
|
|
),
|
|
{ timeout: 60_000, message: 'not every restored SSH tab mounted a PaneManager' }
|
|
)
|
|
.toEqual(expectedTabIds)
|
|
expect(
|
|
await orcaPage.evaluate(
|
|
(ids) =>
|
|
ids.filter((tabId) => window.__terminalParkingDebug?.parkedTabIds().includes(tabId)),
|
|
expectedTabIds
|
|
)
|
|
).toEqual([])
|
|
const afterReload = await readRemoteTerminalTabs(orcaPage, remote.worktreeId)
|
|
expect(afterReload.map((tab) => tab.id).sort()).toEqual(expectedTabIds)
|
|
expect(afterReload.map((tab) => tab.ptyId).sort()).toEqual(
|
|
beforeReload.map((tab) => tab.ptyId).sort()
|
|
)
|
|
|
|
const firstTabId = beforeReload[0]?.id
|
|
if (!firstTabId) {
|
|
throw new Error('Restored SSH tabs disappeared')
|
|
}
|
|
await orcaPage.getByRole('button', { name: /^Terminal 1 Close tab Terminal 1/ }).click()
|
|
await expect
|
|
.poll(() => orcaPage.evaluate(() => window.__store?.getState().activeTabId ?? null), {
|
|
timeout: 10_000
|
|
})
|
|
.toBe(firstTabId)
|
|
await orcaPage.evaluate((tabId) => {
|
|
const manager = window.__paneManagers?.get(tabId)
|
|
const pane = manager?.getActivePane?.() ?? manager?.getPanes?.()[0]
|
|
if (!pane) {
|
|
throw new Error('Restored SSH pane unavailable')
|
|
}
|
|
pane.terminal.options.screenReaderMode = true
|
|
pane.terminal.refresh(0, pane.terminal.rows - 1)
|
|
}, firstTabId)
|
|
|
|
const marker = `SSH_RESTORE_OK_${Date.now()}`
|
|
const proofFile = '/tmp/orca-ssh-restore-proof'
|
|
await focusActiveTerminalInput(orcaPage)
|
|
await orcaPage.keyboard.type(`printf '${marker}' > ${proofFile} && printf '${marker}\\n'`)
|
|
await orcaPage.keyboard.press('Enter')
|
|
await expect(
|
|
orcaPage.locator(
|
|
`[data-terminal-tab-id=${JSON.stringify(firstTabId)}] .xterm-accessibility-tree`
|
|
)
|
|
).toContainText(marker, { timeout: 30_000 })
|
|
expect(execDockerSshRelayTargetCommand(target, `cat ${proofFile}`)).toBe(marker)
|
|
} finally {
|
|
cleanupDockerSshRelayTarget(target)
|
|
}
|
|
})
|
|
})
|