diff --git a/src/main/runtime/headless-hydration-desktop-depth.test.ts b/src/main/runtime/headless-hydration-desktop-depth.test.ts new file mode 100644 index 00000000000..fdb4ae29598 --- /dev/null +++ b/src/main/runtime/headless-hydration-desktop-depth.test.ts @@ -0,0 +1,31 @@ +import { expect, it, vi } from 'vitest' +import { createHydrationRuntime, PTY_ID, SIZE } from './headless-hydration-ownership-test-fixture' + +const LINES = Array.from({ length: 1500 }, (_, i) => `L${String(i + 1).padStart(4, '0')} line`) + +it('hydrates main from the renderer at desktop depth so a hidden-output reveal keeps history', async () => { + const runtime = createHydrationRuntime() + const serialize = vi.fn(async (_ptyId: string, opts?: { scrollbackRows?: number }) => ({ + data: LINES.slice(-((opts?.scrollbackRows ?? 0) + SIZE.rows)).join('\r\n'), + ...SIZE + })) + runtime.setPtyController({ + write: () => true, + kill: () => true, + getForegroundProcess: async () => null, + getSize: () => SIZE, + hasRendererSerializer: () => true, + serializeBuffer: serialize + }) + + runtime.onPtyData(PTY_ID, '\r\n$ ', 1) + await runtime.model().writeChain + + expect(serialize).toHaveBeenCalledWith(PTY_ID, { scrollbackRows: 5000 }) + const snapshot = await runtime.serializeHiddenOutputRecoveryBuffer(PTY_ID, { + scrollbackRows: 5000 + }) + const restored = `${snapshot?.scrollbackAnsi ?? ''}${snapshot?.data ?? ''}` + expect(restored.match(/L\d{4} line/g)).toHaveLength(1500) + expect(restored).toContain('L0001 line') +}) diff --git a/src/main/runtime/scrollback-limits.ts b/src/main/runtime/scrollback-limits.ts index 0970205fd19..cdd7920c906 100644 --- a/src/main/runtime/scrollback-limits.ts +++ b/src/main/runtime/scrollback-limits.ts @@ -1,3 +1,3 @@ -// Bound desktop xterm hydration for phones: replaying the full 50k-row buffer across IPC/headless emulation is expensive (docs/mobile-prefer-renderer-scrollback.md). +// Bound phone snapshots: replaying the full 50k-row buffer across IPC/headless emulation is expensive (docs/mobile-prefer-renderer-scrollback.md). export const MOBILE_SUBSCRIBE_SCROLLBACK_ROWS = 1000 export const MOBILE_SNAPSHOT_BYTE_BUDGET = 512 * 1024 diff --git a/src/main/runtime/terminal/output/headless-terminal-hydration.ts b/src/main/runtime/terminal/output/headless-terminal-hydration.ts index 0a50d4e0b92..55434e8d1c4 100644 --- a/src/main/runtime/terminal/output/headless-terminal-hydration.ts +++ b/src/main/runtime/terminal/output/headless-terminal-hydration.ts @@ -3,7 +3,7 @@ import { splitFreebuffScreenUpdates } from '../../../../shared/freebuff-screen-s import { OrcaRuntimeWithSerializeMainTerminalBuffer } from './terminal-buffer-operations' import { observeFreebuffTerminalStatus } from '../../freebuff-terminal-status' import { observeCodexCapacityTerminalStatus } from '../../codex-capacity-terminal-status' -import { MOBILE_SUBSCRIBE_SCROLLBACK_ROWS } from '../../scrollback-limits' +import { DESKTOP_TERMINAL_SCROLLBACK_ROWS_DEFAULT } from '../../../../shared/terminal-scrollback-policy' import { detectAgentStatusFromTitle, normalizeTerminalTitle @@ -57,8 +57,9 @@ export abstract class OrcaRuntimeWithMaybeHydrateHeadlessFromRenderer extends Or } try { // Retain normal-buffer scrollback while a TUI is in alt-screen so pre-TUI history remains restorable (#6106). + // Desktop depth (this emulator's retention): a hidden-output reveal repaints the pane from this model. const rendered = await controller.serializeBuffer!(ptyId, { - scrollbackRows: MOBILE_SUBSCRIBE_SCROLLBACK_ROWS + scrollbackRows: DESKTOP_TERMINAL_SCROLLBACK_ROWS_DEFAULT }) if ( this.headlessTerminals.get(ptyId) !== state ||