mirror of
https://github.com/stablyai/orca.git
synced 2026-10-16 08:03:09 +00:00
Prototype rich hidden terminal model restore
This commit is contained in:
@@ -85,6 +85,19 @@ describe('shouldSkipHiddenRendererOutput', () => {
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('can opt synchronized chunks into model-backed restore for prototype coverage', () => {
|
||||
expect(
|
||||
shouldSkipHiddenRendererOutput({
|
||||
foreground: false,
|
||||
canRestoreHiddenOutput: true,
|
||||
startupRendererQueryWindowActive: false,
|
||||
synchronizedOutputActive: true,
|
||||
allowSynchronizedModelRestore: true,
|
||||
data: '\x1b[?2026h\x1b[2J\x1b[H╭ rich 😀 ╮\r\n\x1b[?2026l'
|
||||
})
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('keeps startup query windows live', () => {
|
||||
expect(
|
||||
shouldSkipHiddenRendererOutput({
|
||||
|
||||
@@ -3,6 +3,7 @@ export type HiddenRendererSkipEligibility = {
|
||||
canRestoreHiddenOutput: boolean
|
||||
startupRendererQueryWindowActive: boolean
|
||||
synchronizedOutputActive: boolean
|
||||
allowSynchronizedModelRestore?: boolean
|
||||
data: string
|
||||
}
|
||||
|
||||
@@ -116,18 +117,21 @@ export function shouldSkipHiddenRendererOutput({
|
||||
canRestoreHiddenOutput,
|
||||
startupRendererQueryWindowActive,
|
||||
synchronizedOutputActive,
|
||||
allowSynchronizedModelRestore = false,
|
||||
data
|
||||
}: HiddenRendererSkipEligibility): boolean {
|
||||
if (
|
||||
foreground ||
|
||||
!canRestoreHiddenOutput ||
|
||||
startupRendererQueryWindowActive ||
|
||||
// Why: DEC 2026 frames can arrive split across chunks; safe-looking rows
|
||||
// may precede rich table/TUI bytes that need live xterm renderer state.
|
||||
synchronizedOutputActive ||
|
||||
data.length === 0
|
||||
) {
|
||||
return false
|
||||
}
|
||||
if (synchronizedOutputActive) {
|
||||
// Why: release behavior keeps split DEC 2026 frames live. The override is
|
||||
// only for proving model-backed replay before shipping richer hidden skips.
|
||||
return allowSynchronizedModelRestore
|
||||
}
|
||||
return containsOnlyRestorableHiddenOutput(data)
|
||||
}
|
||||
|
||||
@@ -533,6 +533,8 @@ describe('connectPanePty', () => {
|
||||
}
|
||||
delete (globalThis as unknown as { window?: unknown }).window
|
||||
delete (globalThis as Record<string, unknown>).__ptyConnectDiag
|
||||
delete (globalThis as Record<string, unknown>)
|
||||
.__ORCA_TEST_ALLOW_SYNCHRONIZED_HIDDEN_MODEL_RESTORE__
|
||||
})
|
||||
|
||||
it('does not retain PTY connect diagnostics unless e2e debug state is enabled', async () => {
|
||||
@@ -3132,6 +3134,82 @@ describe('connectPanePty', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('can prototype hidden rich synchronized restore from the headless model', async () => {
|
||||
;(globalThis as Record<string, unknown>).__ORCA_TEST_ALLOW_SYNCHRONIZED_HIDDEN_MODEL_RESTORE__ =
|
||||
true
|
||||
const { connectPanePty } = await import('./pty-connection')
|
||||
const transport = createMockTransport('pty-id')
|
||||
const capturedDataCallback: {
|
||||
current: ((data: string, meta?: { seq?: number; rawLength?: number }) => void) | null
|
||||
} = { current: null }
|
||||
transport.connect.mockImplementation(async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
|
||||
capturedDataCallback.current = callbacks.onData ?? null
|
||||
return 'pty-id'
|
||||
})
|
||||
transportFactoryQueue.push(transport)
|
||||
const getMainBufferSnapshot = window.api.pty.getMainBufferSnapshot as unknown as ReturnType<
|
||||
typeof vi.fn
|
||||
>
|
||||
const richHiddenFrame = [
|
||||
'\x1b[?2026h',
|
||||
'\x1b[?1049h',
|
||||
'\x1b[2J\x1b[H',
|
||||
'\x1b[?25l',
|
||||
'\x1b[2;36m╭────────────────────────────╮\x1b[0m\r\n',
|
||||
'\x1b[2;36m│ model-backed rich 😀 ███░ │\x1b[0m\r\n',
|
||||
'\x1b[2;36m╰────────────────────────────╯\x1b[0m',
|
||||
'\x1b[6;4H\x1b[?25h',
|
||||
'\x1b[?2026l'
|
||||
].join('')
|
||||
const visibleTrigger = 'visible-trigger\r\n'
|
||||
getMainBufferSnapshot.mockResolvedValue({
|
||||
data: richHiddenFrame,
|
||||
cols: 96,
|
||||
rows: 18,
|
||||
seq: richHiddenFrame.length + visibleTrigger.length,
|
||||
source: 'headless'
|
||||
})
|
||||
|
||||
const pane = createPane(1)
|
||||
const refresh = vi.fn()
|
||||
const terminal = pane.terminal as typeof pane.terminal & {
|
||||
_core?: { refresh: typeof refresh }
|
||||
}
|
||||
terminal._core = { refresh }
|
||||
terminal.write = vi.fn((_data: string, callback?: () => void) => {
|
||||
callback?.()
|
||||
})
|
||||
const manager = createManager(1)
|
||||
const deps = createDeps({
|
||||
isVisibleRef: { current: false }
|
||||
})
|
||||
const disposable = connectPanePty(pane as never, manager as never, deps as never)
|
||||
await flushAsyncTicks(6)
|
||||
|
||||
capturedDataCallback.current?.(richHiddenFrame, {
|
||||
seq: richHiddenFrame.length,
|
||||
rawLength: richHiddenFrame.length
|
||||
})
|
||||
await flushAsyncTicks(2)
|
||||
|
||||
expect(pane.terminal.write).not.toHaveBeenCalled()
|
||||
expect(getMainBufferSnapshot).not.toHaveBeenCalled()
|
||||
|
||||
;(deps.isVisibleRef as { current: boolean }).current = true
|
||||
capturedDataCallback.current?.(visibleTrigger, {
|
||||
seq: richHiddenFrame.length + visibleTrigger.length,
|
||||
rawLength: visibleTrigger.length
|
||||
})
|
||||
await flushAsyncTicks(20)
|
||||
|
||||
expect(getMainBufferSnapshot).toHaveBeenCalledWith('pty-id', { scrollbackRows: 5000 })
|
||||
expect(pane.terminal.resize).toHaveBeenCalledWith(96, 18)
|
||||
expect(pane.terminal.write).toHaveBeenCalledWith(richHiddenFrame, expect.any(Function))
|
||||
expect(pane.terminal.write).not.toHaveBeenCalledWith(visibleTrigger, expect.any(Function))
|
||||
expect(refresh).toHaveBeenCalledWith(0, 39, true)
|
||||
disposable.dispose()
|
||||
})
|
||||
|
||||
it('queues visible split-pane PTY bytes when the pane is not active', async () => {
|
||||
const { connectPanePty } = await import('./pty-connection')
|
||||
const transport = createMockTransport()
|
||||
|
||||
@@ -420,6 +420,15 @@ function recordPtyConnectDiagnostic(message: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
function shouldAllowPrototypeSynchronizedHiddenModelRestore(): boolean {
|
||||
const target = globalThis as {
|
||||
__ORCA_TEST_ALLOW_SYNCHRONIZED_HIDDEN_MODEL_RESTORE__?: boolean
|
||||
}
|
||||
return (
|
||||
import.meta.env.DEV && target.__ORCA_TEST_ALLOW_SYNCHRONIZED_HIDDEN_MODEL_RESTORE__ === true
|
||||
)
|
||||
}
|
||||
|
||||
// Why: when multiple panes/tabs need the same deferred SSH connection,
|
||||
// the first one calls ssh.connect() and subsequent ones must wait for it
|
||||
// rather than returning early (which would leave them disconnected). This
|
||||
@@ -2609,6 +2618,7 @@ export function connectPanePty(
|
||||
canRestoreHiddenOutput: canUseHiddenOutputSnapshot(transport.getPtyId()),
|
||||
startupRendererQueryWindowActive: isHiddenStartupRendererQueryWindowActive(),
|
||||
synchronizedOutputActive: synchronizedHiddenOutput,
|
||||
allowSynchronizedModelRestore: shouldAllowPrototypeSynchronizedHiddenModelRestore(),
|
||||
data
|
||||
})
|
||||
if (shouldSkipHiddenOutput) {
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
} from './helpers/terminal'
|
||||
|
||||
type HiddenTuiWindow = Window & {
|
||||
__ORCA_TEST_ALLOW_SYNCHRONIZED_HIDDEN_MODEL_RESTORE__?: boolean
|
||||
__terminalPtyDataInjection?: {
|
||||
inject: (paneKey: string, data: string, meta?: { seq?: number; rawLength?: number }) => boolean
|
||||
}
|
||||
@@ -82,6 +83,15 @@ async function resetHiddenDebug(page: Page): Promise<void> {
|
||||
})
|
||||
}
|
||||
|
||||
async function setPrototypeSynchronizedHiddenModelRestore(
|
||||
page: Page,
|
||||
enabled: boolean
|
||||
): Promise<void> {
|
||||
await page.evaluate((enabled) => {
|
||||
;(window as HiddenTuiWindow).__ORCA_TEST_ALLOW_SYNCHRONIZED_HIDDEN_MODEL_RESTORE__ = enabled
|
||||
}, enabled)
|
||||
}
|
||||
|
||||
function writeHiddenFrameScript(scriptPath: string, runId: string): void {
|
||||
const frames = Array.from({ length: 25 }, (_, frame) => tuiFrame(runId, frame))
|
||||
writeFileSync(
|
||||
@@ -379,6 +389,98 @@ test.describe('Hidden terminal TUI visual restore', () => {
|
||||
rmSync(scriptPath, { force: true })
|
||||
})
|
||||
|
||||
test('prototypes rich synchronized TUI restore from the headless model', async ({
|
||||
orcaPage,
|
||||
testRepoPath
|
||||
}, testInfo: TestInfo) => {
|
||||
await waitForSessionReady(orcaPage)
|
||||
const firstWorktreeId = await waitForActiveWorktree(orcaPage)
|
||||
const secondWorktreeId = (await getAllWorktreeIds(orcaPage)).find(
|
||||
(id) => id !== firstWorktreeId
|
||||
)
|
||||
test.skip(!secondWorktreeId, 'hidden TUI restore needs the seeded secondary worktree')
|
||||
if (!secondWorktreeId) {
|
||||
return
|
||||
}
|
||||
|
||||
await switchToWorktree(orcaPage, secondWorktreeId)
|
||||
await ensureTerminalVisible(orcaPage)
|
||||
await waitForActiveTerminalManager(orcaPage, 30_000)
|
||||
const hiddenSnapshot = await waitForPaneIdentitySnapshot(orcaPage, 1)
|
||||
const hiddenPane = hiddenSnapshot.panes[0]
|
||||
if (!hiddenPane?.ptyId) {
|
||||
throw new Error('hidden rich model prototype pane did not bind a PTY')
|
||||
}
|
||||
await switchToWorktree(orcaPage, firstWorktreeId)
|
||||
await expect
|
||||
.poll(() => getActiveWorktreeId(orcaPage), {
|
||||
timeout: 10_000,
|
||||
message: 'first worktree did not become active before hidden rich model prototype'
|
||||
})
|
||||
.toBe(firstWorktreeId)
|
||||
|
||||
const runId = randomUUID()
|
||||
const finalMarker = `VISUAL_RESTORE_FINAL_${runId}_24`
|
||||
const scriptPath = path.join(testRepoPath, `.orca-hidden-rich-model-${runId}.mjs`)
|
||||
writeHiddenFrameScript(scriptPath, runId)
|
||||
await resetHiddenDebug(orcaPage)
|
||||
await setPrototypeSynchronizedHiddenModelRestore(orcaPage, true)
|
||||
try {
|
||||
await writeHiddenFrames(orcaPage, hiddenPane.ptyId, scriptPath)
|
||||
await resetHiddenDebug(orcaPage)
|
||||
|
||||
await expect
|
||||
.poll(async () => (await readHiddenDebug(orcaPage))?.hiddenRendererSkipCount ?? 0, {
|
||||
timeout: 10_000,
|
||||
message: 'prototype rich hidden TUI output should skip renderer writes'
|
||||
})
|
||||
.toBeGreaterThan(0)
|
||||
await expect
|
||||
.poll(() => readMainSnapshotSource(orcaPage, hiddenPane.ptyId!), {
|
||||
timeout: 10_000,
|
||||
message: 'prototype rich hidden TUI source did not come from headless model'
|
||||
})
|
||||
.toBe('headless')
|
||||
|
||||
await switchToWorktree(orcaPage, secondWorktreeId)
|
||||
await ensureTerminalVisible(orcaPage)
|
||||
await waitForActiveTerminalManager(orcaPage, 30_000)
|
||||
|
||||
await expect
|
||||
.poll(() => getTerminalContent(orcaPage, 12_000), {
|
||||
timeout: 10_000,
|
||||
message: 'prototype rich headless TUI frame did not restore when visible'
|
||||
})
|
||||
.toContain(finalMarker)
|
||||
|
||||
const content = await getTerminalContent(orcaPage, 12_000)
|
||||
expect(content).toContain(`Frame 024`)
|
||||
expect(content).toContain('╭')
|
||||
expect(content).toContain('├')
|
||||
expect(content).toContain('█')
|
||||
expect(content).not.toContain('Orca skipped hidden terminal output')
|
||||
await expect
|
||||
.poll(() => readTuiCursorState(orcaPage), {
|
||||
timeout: 5_000,
|
||||
message: 'prototype rich headless TUI cursor stayed hidden after restore'
|
||||
})
|
||||
.toMatchObject({
|
||||
hidden: false,
|
||||
initialized: true
|
||||
})
|
||||
|
||||
const screenshotPath = testInfo.outputPath('hidden-rich-model-restore-final.png')
|
||||
await orcaPage.screenshot({ path: screenshotPath, fullPage: true })
|
||||
await testInfo.attach('hidden-rich-model-restore-final.png', {
|
||||
path: screenshotPath,
|
||||
contentType: 'image/png'
|
||||
})
|
||||
} finally {
|
||||
await setPrototypeSynchronizedHiddenModelRestore(orcaPage, false)
|
||||
rmSync(scriptPath, { force: true })
|
||||
}
|
||||
})
|
||||
|
||||
test('keeps hidden terminal side effects live while hidden output may restore', async ({
|
||||
orcaPage
|
||||
}) => {
|
||||
|
||||
Reference in New Issue
Block a user