test: remove low-value terminal coordination and startup cases (#40512)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
OrcaWin
2026-11-09 15:11:59 -08:00
committed by GitHub
co-authored by OrcaWin
parent 2783f8e461
commit f4e4862f00
18 changed files with 10 additions and 2747 deletions
File diff suppressed because one or more lines are too long
@@ -5878,7 +5878,6 @@
"src/renderer/src/components/terminal-pane/pty-connection/output-restore/pty-connection-post-dispose-restore-termination.test.ts": 1215,
"src/renderer/src/components/terminal-pane/pty-connection/agent-identity/pty-connection-process-exit-row-retire.test.ts": 10626,
"src/renderer/src/components/terminal-pane/pty-connection/host-ownership/pty-connection-owner-hydration-backend-retention.test.ts": 4181,
"src/renderer/src/components/terminal-pane/pty-connection/startup/pty-connection-queued-startup-consume.test.ts": 4181,
"src/renderer/src/components/terminal-pane/pty-connection/reattach/pty-connection-reattach-binding.test.ts": 5240,
"src/renderer/src/components/terminal-pane/pty-connection/input/pty-connection-remote-new-terminal-typeahead.test.ts": 1864,
"src/renderer/src/components/terminal-pane/pty-connection/reattach/pty-connection-remote-snapshot-image-title.test.ts": 1754,
@@ -5895,7 +5894,6 @@
"src/shared/terminal/protocol/escape/terminal-synchronized-output-socket-splits.test.ts": 92,
"src/renderer/src/components/terminal-pane/pty-connection/output/foreground-renderer-risk-scan-tail.test.ts": 111,
"src/renderer/src/components/terminal-pane/pty-connection/spawn/hidden-agent-resume-deferral.test.ts": 55,
"src/renderer/src/components/terminal-pane/pty-connection/startup/in-place-resume-hold-wiring.test.ts": 2484,
"src/renderer/src/components/terminal-pane/pty-connection/startup/main-pane-resume-banner.test.ts": 59,
"src/renderer/src/components/terminal-pane/pty-connection/spawn/main-pane-start-wait.test.ts": 76,
"src/renderer/src/components/terminal-pane/pty-connection/serialization/pane-serializer-register.test.ts": 123,
@@ -221,42 +221,6 @@ export function registerPtyConnectionHiddenBacklogReconciliationTests(): void {
}
})
it('holds the post-flood repaint while the user reads scrollback and runs it on return to the bottom', async () => {
const { pane, dataCallback, getMainBufferSnapshot, resolveFirstSnapshot } =
await startInFlightRestore()
const { markTerminalFollowOutput, markTerminalPinnedViewport } =
await import('@/lib/pane-manager/terminal-scroll-intent')
pane.terminal.buffer.active.viewportY = 42
pane.terminal.buffer.active.baseY = 100
markTerminalPinnedViewport(pane.terminal)
dataCallback('f'.repeat(300 * 1024), { seq: 300 * 1024 + 64, rawLength: 300 * 1024 })
dataCallback('g'.repeat(300 * 1024), { seq: 600 * 1024 + 64, rawLength: 300 * 1024 })
try {
vi.useFakeTimers()
resolveFirstSnapshot({ data: 'flood snapshot\r\n', cols: 100, rows: 30, seq: 64 })
await flushAsyncTicks(20)
expect(getMainBufferSnapshot).toHaveBeenCalledTimes(1)
// Quiet flood, but the user is still scrolled back: the clear-and-replay repaint must not move their viewport.
vi.advanceTimersByTime(2_100)
await flushAsyncTicks(20)
expect(getMainBufferSnapshot).toHaveBeenCalledTimes(1)
vi.advanceTimersByTime(60_000)
await flushAsyncTicks(20)
expect(getMainBufferSnapshot).toHaveBeenCalledTimes(1)
// Returning to the bottom is the event that releases it — the heal is deferred, never dropped.
pane.terminal.buffer.active.viewportY = pane.terminal.buffer.active.baseY
markTerminalFollowOutput(pane.terminal)
await flushAsyncTicks(20)
expect(getMainBufferSnapshot).toHaveBeenCalledTimes(2)
} finally {
vi.useRealTimers()
}
})
it('sends salvaged queries immediately from an overflowing restore queue', async () => {
const { pane, transport, dataCallback } = await startInFlightRestore()
@@ -280,38 +244,6 @@ export function registerPtyConnectionHiddenBacklogReconciliationTests(): void {
expect(written).not.toContain('aaaa')
expect(written).not.toContain('bbbb')
})
it('keeps the hidden-pane drop sentinel arming a reveal restore (gate unchanged)', async () => {
enableMainAuthority()
const isVisibleRef = { current: false }
const deps = createDeps({ isVisibleRef })
const { pane, dataCallback } = await connectHiddenPane(deps)
const transportOptions = createdTransportOptions.at(-1) as {
onPtySpawn?: (ptyId: string) => void
}
transportOptions.onPtySpawn?.('pty-id')
const getMainBufferSnapshot = vi.mocked(window.api.pty.getMainBufferSnapshot)
getMainBufferSnapshot.mockResolvedValue({
data: 'hidden reveal snapshot\r\n',
cols: 100,
rows: 30,
seq: 64
})
// Hidden pane: the sentinel latches restore-needed but must not fetch.
dataCallback('', { droppedOutput: true })
await flushAsyncTicks(8)
expect(getMainBufferSnapshot).not.toHaveBeenCalled()
// Reveal: the latched restore fetches exactly one snapshot.
isVisibleRef.current = true
const { requestTerminalBacklogRecovery } =
await import('@/lib/pane-manager/pane-terminal-output-scheduler')
requestTerminalBacklogRecovery(pane.terminal as never)
await flushAsyncTicks(20)
expect(getMainBufferSnapshot).toHaveBeenCalledTimes(1)
expect(writtenFloodData(pane)).toContain('hidden reveal snapshot')
})
})
describe('post-restore backlog reconciliation', () => {
@@ -357,20 +289,6 @@ export function registerPtyConnectionHiddenBacklogReconciliationTests(): void {
return pane.terminal.write.mock.calls.map((call) => String(call[0])).join('')
}
it('drops backlog chunks the restored snapshot already covers', async () => {
const { pane, dataCallback } = await restoreVisiblePaneToBaseline()
// Whole chunk at or before the baseline seq: duplicate, never written.
dataCallback('OLD-DUPLICATE', { seq: 60, rawLength: 13 })
await flushAsyncTicks(8)
expect(writtenData(pane)).not.toContain('OLD-DUPLICATE')
// Contiguous post-baseline chunk flows through normally.
dataCallback('NEW', { seq: 67, rawLength: 3 })
await flushAsyncTicks(8)
expect(writtenData(pane)).toContain('NEW')
})
it('records a 2031 subscribe on a chunk the restored snapshot drops as duplicate', async () => {
// Scan before duplicate reconciliation: replay omits mode tracking, leaving this delivery as the only subscription evidence.
enableMainAuthority()
@@ -389,54 +307,6 @@ export function registerPtyConnectionHiddenBacklogReconciliationTests(): void {
expect(transport.sendInput).not.toHaveBeenCalledWith(expect.stringMatching(/\?997/))
})
it('slices a partial overlap when raw and clean lengths match', async () => {
const { pane, dataCallback } = await restoreVisiblePaneToBaseline()
// start seq 61 < baseline 64 < end seq 67 — only the last 3 chars are new.
dataCallback('ABCDEF', { seq: 67, rawLength: 6 })
await flushAsyncTicks(8)
const written = writtenData(pane)
expect(written).toContain('DEF')
expect(written).not.toContain('ABC')
})
it('forces a fresh snapshot for an overlap whose offsets cannot be mapped', async () => {
const { pane, dataCallback, getMainBufferSnapshot } = await restoreVisiblePaneToBaseline()
getMainBufferSnapshot.mockResolvedValue({
data: 'second snapshot\r\n',
cols: 100,
rows: 30,
seq: 80
})
// rawLength (6) !== data.length (4): OSC stripping makes the slice offset unmappable — restore from a fresh snapshot instead.
dataCallback('ABCD', { seq: 67, rawLength: 6 })
await flushAsyncTicks(20)
expect(writtenData(pane)).not.toContain('ABCD')
expect(getMainBufferSnapshot).toHaveBeenCalledTimes(2)
expect(writtenData(pane)).toContain('second snapshot')
})
it('detects a seq gap after restore and forces another restore', async () => {
const { pane, dataCallback, getMainBufferSnapshot } = await restoreVisiblePaneToBaseline()
getMainBufferSnapshot.mockResolvedValue({
data: 'gap-heal snapshot\r\n',
cols: 100,
rows: 30,
seq: 120
})
// Why: a chunk starting past the continuity point means main trimmed bytes after the overflow marker — only the model snapshot can heal the gap.
dataCallback('AFTER-GAP', { seq: 96, rawLength: 9 })
await flushAsyncTicks(20)
expect(writtenData(pane)).not.toContain('AFTER-GAP')
expect(getMainBufferSnapshot).toHaveBeenCalledTimes(2)
expect(writtenData(pane)).toContain('gap-heal snapshot')
})
it('writes genuinely-new live output whose seq sits below an empty-backlog baseline', async () => {
// E2E twin: a live chunk's seq sits below main's cumulative baseline, but with an empty pending queue main can never re-deliver — so it must write, not drop.
enableMainAuthority()
@@ -1,6 +1,5 @@
import { bindPtyConnectionTestMocks } from '../test-fixtures/pty-connection-mocks.spec'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { RESET_AFTER_BYTE_GAP } from '../../../../../../shared/terminal/protocol/input-modes/terminal-mode-reset-profiles'
import { flushAsyncTicks, createDeferred } from '../test-fixtures/pty-connection-test-async'
import {
createMockTransport,
@@ -111,28 +110,6 @@ export function registerPtyConnectionHiddenDeliveryGateTests(): void {
return { transport, pane, dataCallback: capturedDataCallback.current!, binding }
}
// Only the restore-needed marker proves a byte gap; reset emulator state here with the cross-chunk parser (STA-4042).
it('closes the emulator state gap when a drop is announced', async () => {
enableMainAuthority()
const deps = createDeps({ isVisibleRef: { current: false } })
const { pane, dataCallback } = await connectHiddenPane(deps)
dataCallback('hidden output\r\n', { seq: 16, rawLength: 16 })
pane.terminal.write.mockClear()
const { _dispatchPtyModelRestoreNeededForTest } =
await import('../../pty-transport/delivery/pty-model-restore-channel')
_dispatchPtyModelRestoreNeededForTest({
id: 'pty-id',
reason: 'hidden-drop',
markerSeq: 64
})
await flushAsyncTicks(4)
const written = pane.terminal.write.mock.calls.map(([data]) => data as string)
const gapReset = written.find((data) => data === RESET_AFTER_BYTE_GAP)
expect(gapReset).toBeDefined()
})
it('marks the PTY hidden on hidden output and clears it before requesting restore on reveal', async () => {
enableMainAuthority()
const deps = createDeps({ isVisibleRef: { current: false } })
@@ -313,90 +290,6 @@ export function registerPtyConnectionHiddenDeliveryGateTests(): void {
expect(setHiddenRendererPty).toHaveBeenLastCalledWith('pty-id', true)
})
it('marks hidden codex panes immediately — no startup renderer-query window remains', async () => {
enableMainAuthority()
const deps = createDeps({
isVisibleRef: { current: false },
startup: { command: 'codex' }
})
const { transport, dataCallback } = await connectHiddenPane(deps)
const setHiddenRendererPty = getSetHiddenRendererPtyMock()
const transportOptions = createdTransportOptions.at(-1) as {
onPtySpawn?: (ptyId: string) => void
}
transportOptions.onPtySpawn?.('pty-id')
const factsHandler =
await import('../../pty-transport/output/terminal-side-effect-facts-handler')
// Hidden Codex startups use the ordinary delivery gate; main owns startup query replies.
dataCallback('startup probe output\r\n')
expect(setHiddenRendererPty).toHaveBeenCalledWith('pty-id', true)
factsHandler._dispatchTerminalSideEffectBatchForTest({
ptyId: 'pty-id',
seq: 8,
facts: [{ kind: '2031-subscribe' }]
})
expect(transport.sendInput).not.toHaveBeenCalledWith(expect.stringMatching(/\?997/))
})
it('never answers a 2031-subscribe fact, hidden or visible', async () => {
// Regression pin (#9993): a subscribe arms future notifications, it is not a query.
enableMainAuthority()
const deps = createDeps({ isVisibleRef: { current: false } })
const { transport } = await connectHiddenPane(deps)
// Simulate spawn completion so the pane registers its fact consumer (mock transport never calls onPtySpawn).
const transportOptions = createdTransportOptions.at(-1) as {
onPtySpawn?: (ptyId: string) => void
}
transportOptions.onPtySpawn?.('pty-id')
const factsHandler =
await import('../../pty-transport/output/terminal-side-effect-facts-handler')
factsHandler._dispatchTerminalSideEffectBatchForTest({
ptyId: 'pty-id',
seq: 12,
facts: [{ kind: '2031-subscribe' }]
})
;(deps.isVisibleRef as { current: boolean }).current = true
factsHandler._dispatchTerminalSideEffectBatchForTest({
ptyId: 'pty-id',
seq: 24,
facts: [{ kind: '2031-subscribe' }]
})
expect(transport.sendInput).not.toHaveBeenCalledWith(expect.stringMatching(/\?997/))
expect(transport.sendInputImmediate).not.toHaveBeenCalledWith(
expect.stringMatching(/\?997/)
)
})
it('registers the fact-observed 2031 subscription for later theme flips', async () => {
enableMainAuthority()
const recordPaneMode2031Subscription = vi.fn()
const deps = createDeps({
isVisibleRef: { current: false },
recordPaneMode2031Subscription
})
const { transport } = await connectHiddenPane(deps)
const transportOptions = createdTransportOptions.at(-1) as {
onPtySpawn?: (ptyId: string) => void
}
transportOptions.onPtySpawn?.('pty-id')
const factsHandler =
await import('../../pty-transport/output/terminal-side-effect-facts-handler')
factsHandler._dispatchTerminalSideEffectBatchForTest({
ptyId: 'pty-id',
seq: 12,
facts: [{ kind: '2031-subscribe' }]
})
// Why: without the registry write, maybePushMode2031Flip won't push CSI 997 after a theme change, so the TUI keeps a stale theme.
expect(recordPaneMode2031Subscription).toHaveBeenCalledWith(1, 'dark')
expect(transport.sendInput).not.toHaveBeenCalledWith(expect.stringMatching(/\?997/))
})
it('retires the fact-registered subscription when the TUI withdraws it', async () => {
// Gated panes never receive withdrawal bytes; only this fact can retire a subscription before theme flips send replies to the replacement shell.
enableMainAuthority()
@@ -435,107 +328,6 @@ export function registerPtyConnectionHiddenDeliveryGateTests(): void {
expect(paneLastThemeModeRef.current.get(1)).toBeUndefined()
})
it('leaves the chunk scanner silent on a gate-managed PTY', async () => {
enableMainAuthority()
const deps = createDeps({ isVisibleRef: { current: false } })
const { transport, dataCallback } = await connectHiddenPane(deps)
const before = transport.sendInput.mock.calls.length
dataCallback('\x1b[?2031h')
const replies = transport.sendInput.mock.calls
.slice(before)
.flat()
.filter((arg) => String(arg).includes('997'))
expect(replies).toEqual([])
})
it('declares hidden-at-spawn on connect for hidden panes', async () => {
enableMainAuthority()
const deps = createDeps({ isVisibleRef: { current: false } })
const { transport } = await connectHiddenPane(deps)
// Why: mark the PTY hidden before its first byte, closing the spawn-time DA1-loss window where neither side replied.
expect(transport.connect).toHaveBeenCalledWith(
expect.objectContaining({ initiallyHidden: true })
)
})
it('keeps visible spawns undeclared (visible spawn unchanged)', async () => {
enableMainAuthority()
const deps = createDeps()
const { transport } = await connectHiddenPane(deps)
expect(transport.connect.mock.calls[0]![0]).not.toHaveProperty('initiallyHidden')
})
it('declares hidden-at-spawn for hidden codex panes too', async () => {
enableMainAuthority()
const deps = createDeps({
isVisibleRef: { current: false },
startup: { command: 'codex' }
})
const { transport } = await connectHiddenPane(deps)
// Codex startup is main-owned from byte zero; no temporary renderer reply window remains (pty.test.ts DA1-from-model).
expect(transport.connect).toHaveBeenCalledWith(
expect.objectContaining({ initiallyHidden: true })
)
})
it('does not gate or fact-reply when the hidden-delivery kill switch is off', async () => {
enableMainAuthority()
mockStoreState.settings = {
...mockStoreState.settings,
terminalHiddenDeliveryGate: false
} as StoreState['settings']
const deps = createDeps({ isVisibleRef: { current: false } })
const { transport, dataCallback } = await connectHiddenPane(deps)
const transportOptions = createdTransportOptions.at(-1) as {
onPtySpawn?: (ptyId: string) => void
}
transportOptions.onPtySpawn?.('pty-id')
const setHiddenRendererPty = getSetHiddenRendererPtyMock()
dataCallback('hidden output\r\n')
expect(setHiddenRendererPty).not.toHaveBeenCalled()
// Why: gate off keeps the byte-scan responder authoritative — the fact must not produce a second reply.
const factsHandler =
await import('../../pty-transport/output/terminal-side-effect-facts-handler')
factsHandler._dispatchTerminalSideEffectBatchForTest({
ptyId: 'pty-id',
seq: 12,
facts: [{ kind: '2031-subscribe' }]
})
expect(transport.sendInput).not.toHaveBeenCalled()
})
it('clears a marked-hidden PTY on dispose so a remount is never gated', async () => {
enableMainAuthority()
const deps = createDeps({ isVisibleRef: { current: false } })
const { dataCallback, binding } = await connectHiddenPane(deps)
const setHiddenRendererPty = getSetHiddenRendererPtyMock()
dataCallback('hidden output\r\n')
expect(setHiddenRendererPty).toHaveBeenLastCalledWith('pty-id', true)
binding.dispose()
expect(setHiddenRendererPty).toHaveBeenLastCalledWith('pty-id', false)
})
it('never treats a live chunk that strips to empty as a restore marker', async () => {
// Why: an all-OSC-9999 chunk arrives as '' after stripping; only pty:modelRestoreNeeded may trigger a restore, not the empty byte path.
enableMainAuthority()
const deps = createDeps({ isVisibleRef: { current: true } })
const { dataCallback } = await connectHiddenPane(deps)
const getMainBufferSnapshot = window.api.pty.getMainBufferSnapshot as unknown as ReturnType<
typeof vi.fn
>
dataCallback('', { seq: 32, rawLength: 24 })
await flushAsyncTicks(20)
expect(getMainBufferSnapshot).not.toHaveBeenCalled()
})
it('gates marker re-arms during an in-flight foreground restore and repaints once after', async () => {
enableMainAuthority()
const deps = createDeps({ isVisibleRef: { current: true } })
@@ -356,61 +356,6 @@ export function registerPtyConnectionHiddenOutputRestoreTests(): void {
binding.dispose()
})
it('writes ordinary hidden output live instead of proactively restoring a snapshot', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks4 = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
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 hidden = 'small-hidden-output\r\n'
const live = 'visible-after-hidden\r\n'
getMainBufferSnapshot.mockResolvedValue({
data: `snapshot-with-${hidden}`,
cols: 100,
rows: 30,
seq: hidden.length + live.length
})
const pane = createPane(1)
const manager = createManager(1)
const deps = createDeps({
isVisibleRef: { current: false }
})
const disposable = connectPanePty(
pane as never,
manager as never,
bindPaneAgentTitleMocks(deps as never, agentTitleMocks4)
)
await flushAsyncTicks(6)
capturedDataCallback.current?.(hidden, { seq: hidden.length, rawLength: hidden.length })
expect(pane.terminal.write).not.toHaveBeenCalledWith(hidden, expect.any(Function))
;(deps.isVisibleRef as { current: boolean }).current = true
capturedDataCallback.current?.(live, {
seq: hidden.length + live.length,
rawLength: live.length
})
await flushAsyncTicks(20)
expect(getMainBufferSnapshot).not.toHaveBeenCalled()
expect(pane.terminal.write).toHaveBeenCalledWith(hidden)
expect(pane.terminal.write).toHaveBeenCalledWith(live, expect.any(Function))
disposable.dispose()
})
it('pauses capable paired output while hidden and restores exactly on reveal', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks5 = await vi.importMock<PtyConnectionAgentTitleMocks>(
@@ -53,62 +53,6 @@ export function registerPtyConnectionHiddenSnapshotLiveOverlapTests(): void {
await restoreTerminalTestGlobals()
})
it('writes foreground chunks that are newer than the restored main snapshot', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
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 hidden = 'x'.repeat(2 * 1024 * 1024 + 1)
const live = 'new-live-output\r\n'
getMainBufferSnapshot.mockResolvedValue({
data: 'snapshot-before-live\r\n',
cols: 120,
rows: 40,
seq: hidden.length
})
const pane = createPane(1)
const manager = createManager(1)
const deps = createDeps({
isVisibleRef: { current: false }
})
const disposable = connectPanePty(
pane as never,
manager as never,
bindPaneAgentTitleMocks(deps as never, agentTitleMocks)
)
await flushAsyncTicks(6)
capturedDataCallback.current?.(hidden, { seq: hidden.length, rawLength: hidden.length })
;(deps.isVisibleRef as { current: boolean }).current = true
capturedDataCallback.current?.(live, {
seq: hidden.length + live.length,
rawLength: live.length
})
await flushAsyncTicks(20)
expect(pane.terminal.write).toHaveBeenCalledWith(
'snapshot-before-live\r\n',
expect.any(Function)
)
expect(pane.terminal.write).toHaveBeenCalledWith(live, expect.any(Function))
disposable.dispose()
})
it('writes only the live chunk suffix not covered by the restored main snapshot', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks2 = await vi.importMock<PtyConnectionAgentTitleMocks>(
@@ -2,7 +2,6 @@ import { bindPtyConnectionTestMocks } from '../test-fixtures/pty-connection-mock
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { flushAsyncTicks, createDeferred } from '../test-fixtures/pty-connection-test-async'
import {
LEAF_1,
captureCallbackTerminalWrites,
createMockTransport,
createPane,
@@ -170,63 +169,6 @@ export function registerPtyConnectionHiddenSnapshotResizeSignalsTests(): void {
disposable.dispose()
})
it('does not signal SIGWINCH after hidden-backlog snapshot replay when dimensions are unchanged', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks2 = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
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 signalPty = window.api.pty.signal as unknown as ReturnType<typeof vi.fn>
const hidden = 'x'.repeat(2 * 1024 * 1024 + 1)
const live = 'visible-after\r\n'
getMainBufferSnapshot.mockResolvedValue({
data: 'snapshot-state\r\n',
cols: 120,
rows: 40,
seq: hidden.length + live.length
})
const pane = createPane(1)
const manager = createManager(1)
const deps = createDeps({
isVisibleRef: { current: false }
})
const disposable = connectPanePty(
pane as never,
manager as never,
bindPaneAgentTitleMocks(deps as never, agentTitleMocks2)
)
await flushAsyncTicks(6)
transport.resize.mockClear()
signalPty.mockClear()
capturedDataCallback.current?.(hidden, { seq: hidden.length, rawLength: hidden.length })
;(deps.isVisibleRef as { current: boolean }).current = true
capturedDataCallback.current?.(live, {
seq: hidden.length + live.length,
rawLength: live.length
})
await flushAsyncTicks(20)
expect(pane.terminal.write).toHaveBeenCalledWith('snapshot-state\r\n', expect.any(Function))
expect(transport.resize).not.toHaveBeenCalledWith(120, 40)
expect(signalPty).not.toHaveBeenCalledWith('pty-id', 'SIGWINCH')
disposable.dispose()
})
it('writes a background-origin alternate-screen repaint that arrives after the reveal restore', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks3 = await vi.importMock<PtyConnectionAgentTitleMocks>(
@@ -400,70 +342,5 @@ export function registerPtyConnectionHiddenSnapshotResizeSignalsTests(): void {
expect(transport.resize).not.toHaveBeenCalledWith(132, 40)
disposable.dispose()
})
it('restores a dropped daemon alt frame from the model when the fit lands back on the capture grid', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks5 = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
const transport = createMockTransport('tab-pty')
transport.connect.mockImplementation(async ({ sessionId }: { sessionId?: string }) =>
sessionId
? {
id: sessionId,
snapshot: 'PREFIX-SCROLLBACK' + 'ALT-FRAME-BODY',
snapshotPrefixAnsi: 'PREFIX-SCROLLBACK',
snapshotFrameAnsi: 'ALT-FRAME-BODY',
snapshotFrameRestoreAnsi: 'RESTORE-LIVE-STATE',
snapshotCols: 200,
snapshotRows: 50,
isAlternateScreen: true
}
: null
)
transportFactoryQueue.push(transport)
mockStoreState = {
...mockStoreState,
tabsByWorktree: { 'wt-1': [{ id: 'tab-1', ptyId: 'tab-pty' }] }
}
const getMainBufferSnapshot = vi.mocked(window.api.pty.getMainBufferSnapshot)
getMainBufferSnapshot.mockResolvedValue({
data: '\x1b[?1049hMODEL-FRAME',
scrollbackAnsi: '',
frameRestoreAnsi: 'MODEL-RESTORE',
cols: 200,
rows: 50,
seq: 10,
alternateScreen: true
})
const pane = createPane(1)
pane.terminal.cols = 200
pane.terminal.rows = 50
const writtenText = (): string => pane.terminal.write.mock.calls.map((c) => c[0]).join('')
// The replay measures a transient narrower grid; the fit then lands back on the capture grid.
pane.fitAddon.proposeDimensions = vi.fn(() =>
writtenText().includes('RESTORE-LIVE-STATE')
? { cols: 200, rows: 50 }
: { cols: 120, rows: 40 }
)
const manager = createManager(1)
const deps = createDeps({
restoredLeafId: LEAF_1,
restoredPtyIdByLeafId: { [LEAF_1]: 'tab-pty' }
})
const disposable = connectPanePty(
pane as never,
manager as never,
bindPaneAgentTitleMocks(deps as never, agentTitleMocks5)
)
await flushAsyncTicks(40)
expect(writtenText()).not.toContain('ALT-FRAME-BODY')
expect(getMainBufferSnapshot).toHaveBeenCalledWith('tab-pty', expect.anything())
await vi.waitFor(() => expect(writtenText()).toContain('MODEL-FRAME'))
disposable.dispose()
})
})
}
@@ -1,22 +1,17 @@
import { bindPtyConnectionTestMocks } from '../test-fixtures/pty-connection-mocks.spec'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import {
POST_REPLAY_LIVE_AGENT_REATTACH_RESET,
POST_REPLAY_REATTACH_RESET,
RESET_TERMINAL_CURSOR_STYLE
} from '../../../../../../shared/terminal/protocol/input-modes/terminal-mode-reset-profiles'
import { replayEpilogue } from '../test-fixtures/pty-connection-test-replay-epilogue'
import { flushAsyncTicks } from '../test-fixtures/pty-connection-test-async'
import {
NORMAL_BUFFER_PROLOGUE,
ANSI_POSITIONED_CURSOR_AGENT_REATTACH_SCREEN
} from '../test-fixtures/pty-connection-test-constants'
import { ANSI_POSITIONED_CURSOR_AGENT_REATTACH_SCREEN } from '../test-fixtures/pty-connection-test-constants'
import {
withMockedDocumentActiveElement,
configureTerminalFocusMode
} from '../test-fixtures/pty-connection-test-dom'
import {
terminalWriteText,
createMockTransport,
createPane,
captureCallbackTerminalWrites,
@@ -83,68 +78,6 @@ export function registerPtyConnectionReplayPayloadHandlingTests(): void {
await restoreTerminalTestGlobals()
})
it('preserves a scrolled-up viewport after hidden-backlog snapshot replay', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
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 hidden = 'x'.repeat(2 * 1024 * 1024 + 1)
const live = 'visible-after\r\n'
getMainBufferSnapshot.mockResolvedValue({
data: 'snapshot-state\r\n',
cols: 100,
rows: 30,
seq: hidden.length + live.length
})
const pane = createPane(1)
pane.terminal.buffer.active.viewportY = 42
pane.terminal.buffer.active.baseY = 100
pane.terminal.write.mockImplementation((data: string | Uint8Array, callback?: () => void) => {
data = terminalWriteText(data)
if (data.includes('snapshot-state')) {
pane.terminal.buffer.active.viewportY = 0
}
callback?.()
})
const manager = createManager(1)
const deps = createDeps({
isVisibleRef: { current: false }
})
const disposable = connectPanePty(
pane as never,
manager as never,
bindPaneAgentTitleMocks(deps as never, agentTitleMocks)
)
await flushAsyncTicks(6)
capturedDataCallback.current?.(hidden, { seq: hidden.length, rawLength: hidden.length })
;(deps.isVisibleRef as { current: boolean }).current = true
capturedDataCallback.current?.(live, {
seq: hidden.length + live.length,
rawLength: live.length
})
await flushAsyncTicks(20)
expect(pane.terminal.write).toHaveBeenCalledWith('snapshot-state\r\n', expect.any(Function))
expect(pane.terminal.scrollToLine).toHaveBeenCalledWith(42)
disposable.dispose()
})
it('cancels a delayed snapshot scroll restore when the pane binding is disposed', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks2 = await vi.importMock<PtyConnectionAgentTitleMocks>(
@@ -276,189 +209,6 @@ export function registerPtyConnectionReplayPayloadHandlingTests(): void {
disposable.dispose()
})
it('rebuilds WebGL after remote buffered replay arrives on an already-open pane', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks4 = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
enableActiveRuntimeEnvironment()
const transport = createMockTransport('remote:env-1@@terminal-1')
const capturedReplayCallback: {
current: ((data: string) => void) | null
} = { current: null }
transport.connect.mockImplementation(
async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
capturedReplayCallback.current = callbacks.onReplayData ?? null
return { id: 'remote:env-1@@terminal-1', replay: '' }
}
)
transportFactoryQueue.push(transport)
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 | Uint8Array, callback?: () => void) => {
callback?.()
})
const manager = createManager(1)
const deps = createDeps()
const disposable = connectPanePty(
pane as never,
manager as never,
bindPaneAgentTitleMocks(deps as never, agentTitleMocks4)
)
await flushAsyncTicks(6)
capturedReplayCallback.current?.('remote prompt\r\n$ ')
await flushAsyncTicks(6)
expect(pane.terminal.write).toHaveBeenCalledWith('remote prompt\r\n$ ', expect.any(Function))
expect(refresh).toHaveBeenCalledWith(0, 39, true)
disposable.dispose()
})
it('preserves live agent modes when queued replay data carries the Cursor Agent screen', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
enableActiveRuntimeEnvironment()
const transport = createMockTransport('remote:env-1@@terminal-1')
const capturedReplayCallback: {
current: ((data: string, meta?: { clearBeforeReplay?: boolean }) => void) | null
} = { current: null }
transport.connect.mockImplementation(
async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
capturedReplayCallback.current = callbacks.onReplayData ?? null
return { id: 'remote:env-1@@terminal-1', replay: '' }
}
)
transportFactoryQueue.push(transport)
setReattachPaneTitle('renamed shell')
const pane = createPane(1)
const textarea = {} as HTMLTextAreaElement
configureTerminalFocusMode(pane, textarea)
const manager = createManager(1)
const deps = createDeps()
const disposable = await withMockedDocumentActiveElement(textarea, async () => {
const agentTitleMocks5 = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
const connection = connectPanePty(
pane as never,
manager as never,
bindPaneAgentTitleMocks(deps as never, agentTitleMocks5)
)
await flushAsyncTicks(6)
capturedReplayCallback.current?.(ANSI_POSITIONED_CURSOR_AGENT_REATTACH_SCREEN)
await flushAsyncTicks(12)
expect(pane.terminal.write).toHaveBeenCalledWith(
replayEpilogue(POST_REPLAY_LIVE_AGENT_REATTACH_RESET),
expect.any(Function)
)
expect(transport.sendInput).toHaveBeenCalledWith('\x1b[I', 'query-reply')
return connection
})
disposable.dispose()
})
// Styled headers need CSI stripping; raw-byte lastIndexOf would silently miss this case.
it('still detects the Cursor Agent screen when CSI styling splits the header and the marker', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
enableActiveRuntimeEnvironment()
const transport = createMockTransport('remote:env-1@@terminal-1')
const capturedReplayCallback: {
current: ((data: string, meta?: { clearBeforeReplay?: boolean }) => void) | null
} = { current: null }
transport.connect.mockImplementation(
async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
capturedReplayCallback.current = callbacks.onReplayData ?? null
return { id: 'remote:env-1@@terminal-1', replay: '' }
}
)
transportFactoryQueue.push(transport)
setReattachPaneTitle('renamed shell')
const pane = createPane(1)
const textarea = {} as HTMLTextAreaElement
configureTerminalFocusMode(pane, textarea)
const manager = createManager(1)
const deps = createDeps()
const disposable = await withMockedDocumentActiveElement(textarea, async () => {
const agentTitleMocks6 = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
const connection = connectPanePty(
pane as never,
manager as never,
bindPaneAgentTitleMocks(deps as never, agentTitleMocks6)
)
await flushAsyncTicks(6)
capturedReplayCallback.current?.(
'\x1b[4;3HCursor \x1b[1mAgent\x1b[0m\x1b[9;3H→\x1b[0m Plan, search, build anything'
)
await flushAsyncTicks(12)
expect(pane.terminal.write).toHaveBeenCalledWith(
replayEpilogue(POST_REPLAY_LIVE_AGENT_REATTACH_RESET),
expect.any(Function)
)
return connection
})
disposable.dispose()
})
// Bound the scan to current-screen bytes; headers buried in old scrollback must not classify the live pane.
it('ignores a Cursor Agent screen buried beyond the payload scan tail limit', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
enableActiveRuntimeEnvironment()
const transport = createMockTransport('remote:env-1@@terminal-1')
const capturedReplayCallback: {
current: ((data: string, meta?: { clearBeforeReplay?: boolean }) => void) | null
} = { current: null }
transport.connect.mockImplementation(
async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
capturedReplayCallback.current = callbacks.onReplayData ?? null
return { id: 'remote:env-1@@terminal-1', replay: '' }
}
)
transportFactoryQueue.push(transport)
setReattachPaneTitle('renamed shell')
const pane = createPane(1)
const textarea = {} as HTMLTextAreaElement
configureTerminalFocusMode(pane, textarea)
const manager = createManager(1)
const deps = createDeps()
const disposable = await withMockedDocumentActiveElement(textarea, async () => {
const agentTitleMocks7 = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
const connection = connectPanePty(
pane as never,
manager as never,
bindPaneAgentTitleMocks(deps as never, agentTitleMocks7)
)
await flushAsyncTicks(6)
capturedReplayCallback.current?.(
`${ANSI_POSITIONED_CURSOR_AGENT_REATTACH_SCREEN}${'shell scrollback\r\n'.repeat(20_000)}`
)
await flushAsyncTicks(12)
expect(transport.sendInput.mock.calls.map(([data]) => data)).not.toContain('\x1b[I')
return connection
})
disposable.dispose()
})
it('downgrades a scrollback-only Cursor Agent signal when the parsed viewport shows a shell', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
enableActiveRuntimeEnvironment()
@@ -515,87 +265,6 @@ export function registerPtyConnectionReplayPayloadHandlingTests(): void {
disposable.dispose()
})
it('does not clear restored scrollback when eager metadata replay opts out', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks9 = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
const transport = createMockTransport('pty-id')
const capturedReplayCallback: {
current: ((data: string, meta?: { clearBeforeReplay?: boolean }) => void) | null
} = { current: null }
transport.connect.mockImplementation(
async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
capturedReplayCallback.current = callbacks.onReplayData ?? null
return 'pty-id'
}
)
transportFactoryQueue.push(transport)
const pane = createPane(1)
pane.terminal.write = vi.fn((_data: string | Uint8Array, callback?: () => void) => {
callback?.()
})
const manager = createManager(1)
const deps = createDeps()
const disposable = connectPanePty(
pane as never,
manager as never,
bindPaneAgentTitleMocks(deps as never, agentTitleMocks9)
)
await flushAsyncTicks(6)
capturedReplayCallback.current?.('\x1b]0;Restored title\x07', { clearBeforeReplay: false })
await flushAsyncTicks(6)
expect(pane.terminal.write).not.toHaveBeenCalledWith(
NORMAL_BUFFER_PROLOGUE,
expect.any(Function)
)
expect(pane.terminal.write).toHaveBeenCalledWith(
'\x1b]0;Restored title\x07',
expect.any(Function)
)
disposable.dispose()
})
it('does not write a clear or reset for empty eager metadata replay', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks10 = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
const transport = createMockTransport('pty-id')
const capturedReplayCallback: {
current: ((data: string, meta?: { clearBeforeReplay?: boolean }) => void) | null
} = { current: null }
transport.connect.mockImplementation(
async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
capturedReplayCallback.current = callbacks.onReplayData ?? null
return 'pty-id'
}
)
transportFactoryQueue.push(transport)
const pane = createPane(1)
pane.terminal.write = vi.fn((_data: string | Uint8Array, callback?: () => void) => {
callback?.()
})
const manager = createManager(1)
const deps = createDeps()
const disposable = connectPanePty(
pane as never,
manager as never,
bindPaneAgentTitleMocks(deps as never, agentTitleMocks10)
)
await flushAsyncTicks(6)
capturedReplayCallback.current?.('', { clearBeforeReplay: false })
await flushAsyncTicks(6)
expect(pane.terminal.write).not.toHaveBeenCalled()
disposable.dispose()
})
it('coalesces remote replay payloads that overlap before parsing starts', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks11 = await vi.importMock<PtyConnectionAgentTitleMocks>(
@@ -155,190 +155,6 @@ export function registerPtyConnectionStalledHiddenRestoreTests(): void {
disposable.dispose()
})
// A dropped span can swallow bold reset; abandonment must ground the pen when no snapshot will restore it (STA-4042).
it('clears the SGR pen before draining abandoned foreground chunks', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks2 = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
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 snapshot = createDeferred<{ data: string; cols: number; rows: number; seq: number }>()
getMainBufferSnapshot.mockReturnValue(snapshot.promise)
// The dropped span is where `ESC[22m` would have been; the pen is left bold.
const hidden = '\x1b[1mbold-run-opened-while-hidden\r\n'
const live = 'live-after-reveal\r\n'
const pane = createPane(1)
const manager = createManager(1)
const deps = createDeps({
isVisibleRef: { current: false },
startup: { command: 'codex' }
})
const disposable = connectPanePty(
pane as never,
manager as never,
bindPaneAgentTitleMocks(deps as never, agentTitleMocks2)
)
await flushAsyncTicks(6)
vi.useFakeTimers()
capturedDataCallback.current?.(hidden, { seq: hidden.length, rawLength: hidden.length })
;(deps.isVisibleRef as { current: boolean }).current = true
capturedDataCallback.current?.(live, {
seq: hidden.length + live.length,
rawLength: live.length
})
await flushAsyncTicks(4)
// Let the foreground deadline expire so the restore is abandoned.
vi.advanceTimersByTime(750)
vi.advanceTimersByTime(0)
await flushAsyncTicks(10)
const written = pane.terminal.write.mock.calls.map(([data]) => data as string)
const resetIndex = written.indexOf(ABANDONED_RESTORE_GAP)
const liveIndex = written.findIndex((data) => data.includes('live-after-reveal'))
expect(resetIndex).toBeGreaterThanOrEqual(0)
expect(liveIndex).toBeGreaterThanOrEqual(0)
expect(resetIndex).toBeLessThan(liveIndex)
disposable.dispose()
})
it('grounds byte-gap state before a remote restore re-arms', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks3 = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
const remotePtyId = 'remote:env-1@@terminal-rearm'
const transport = createMockTransport(remotePtyId)
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 remotePtyId
}
)
const snapshot = createDeferred<{ data: string; cols: number; rows: number; seq: number }>()
transport.serializeBuffer = vi.fn().mockReturnValue(snapshot.promise)
transportFactoryQueue.push(transport)
const hidden = 'x'.repeat(2 * 1024 * 1024 + 1)
const live = 'remote-live-after-rearm\r\n'
const pane = createPane(1)
const deps = createDeps({ isVisibleRef: { current: false } })
const disposable = connectPanePty(
pane as never,
createManager(1) as never,
bindPaneAgentTitleMocks(deps as never, agentTitleMocks3)
)
await flushAsyncTicks(6)
vi.useFakeTimers()
capturedDataCallback.current?.(hidden, { seq: hidden.length, rawLength: hidden.length })
;(deps.isVisibleRef as { current: boolean }).current = true
capturedDataCallback.current?.(live, {
seq: hidden.length + live.length,
rawLength: live.length
})
await flushAsyncTicks(4)
vi.advanceTimersByTime(750)
await flushAsyncTicks(10)
const written = pane.terminal.write.mock.calls.map(([data]) => data as string)
const resetIndex = written.indexOf(ABANDONED_RESTORE_GAP)
const liveIndex = written.indexOf(live)
expect(resetIndex).toBeGreaterThanOrEqual(0)
expect(liveIndex).toBeGreaterThan(resetIndex)
// Remote re-arm already grounds the gap; abandonment must not do it again.
expect(written.filter((data) => data === ABANDONED_RESTORE_GAP)).toHaveLength(1)
expect(written.join('')).not.toContain('main recovery was unavailable')
disposable.dispose()
})
it('falls back after repeated null hidden restore retries and drains blocked foreground', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks4 = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
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
>
getMainBufferSnapshot.mockResolvedValue(null)
const hidden = 'hidden-codex-output\r\n'
const live = 'visible-after-null-retries\r\n'
const pane = createPane(1)
const manager = createManager(1)
const deps = createDeps({
isVisibleRef: { current: false },
startup: { command: 'codex' }
})
const disposable = connectPanePty(
pane as never,
manager as never,
bindPaneAgentTitleMocks(deps as never, agentTitleMocks4)
)
await flushAsyncTicks(6)
expect(capturedDataCallback.current).not.toBeNull()
vi.useFakeTimers()
capturedDataCallback.current?.(hidden, { seq: hidden.length, rawLength: hidden.length })
;(deps.isVisibleRef as { current: boolean }).current = true
capturedDataCallback.current?.(live, {
seq: hidden.length + live.length,
rawLength: live.length
})
await flushAsyncTicks(10)
for (let attempt = 0; attempt < 3; attempt++) {
expect(pane.terminal.write).not.toHaveBeenCalledWith(live, expect.any(Function))
vi.advanceTimersByTime(50)
vi.advanceTimersByTime(0)
await flushAsyncTicks(10)
}
const written = pane.terminal.write.mock.calls.map(([data]) => data as string)
const warningIndex = written.findIndex((data) =>
data.includes('main recovery was unavailable')
)
const liveIndex = written.indexOf(live)
expect(getMainBufferSnapshot).toHaveBeenCalledTimes(4)
expect(warningIndex).toBeGreaterThanOrEqual(0)
expect(liveIndex).toBeGreaterThan(warningIndex)
// Ground the abandoned gap once before draining blocked foreground bytes.
expect(written.filter((data) => data === ABANDONED_RESTORE_GAP)).toHaveLength(1)
expect(written.indexOf(ABANDONED_RESTORE_GAP)).toBeLessThan(liveIndex)
disposable.dispose()
})
it('drops pending foreground overflow when a stalled hidden restore falls back', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks5 = await vi.importMock<PtyConnectionAgentTitleMocks>(
@@ -398,191 +214,6 @@ export function registerPtyConnectionStalledHiddenRestoreTests(): void {
disposable.dispose()
})
it('coalesces tiny pending foreground chunks when stalled hidden restore falls back', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks6 = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
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
>
getMainBufferSnapshot.mockReturnValue(
createDeferred<{ data: string; cols: number; rows: number; seq: number }>().promise
)
const hidden = 'hidden-codex-output\r\n'
const chunkCount = 2_000
const liveChunk = 'x'
const pane = createPane(1)
const manager = createManager(1)
const deps = createDeps({
isVisibleRef: { current: false },
startup: { command: 'codex' }
})
const disposable = connectPanePty(
pane as never,
manager as never,
bindPaneAgentTitleMocks(deps as never, agentTitleMocks6)
)
await flushAsyncTicks(6)
expect(capturedDataCallback.current).not.toBeNull()
vi.useFakeTimers()
capturedDataCallback.current?.(hidden, { seq: hidden.length, rawLength: hidden.length })
;(deps.isVisibleRef as { current: boolean }).current = true
for (let index = 0; index < chunkCount; index += 1) {
capturedDataCallback.current?.(liveChunk, {
seq: hidden.length + index + 1,
rawLength: liveChunk.length
})
}
await flushAsyncTicks(4)
vi.advanceTimersByTime(750)
vi.advanceTimersByTime(0)
await flushAsyncTicks(10)
const written = pane.terminal.write.mock.calls.map(([data]) => data as string)
const warningIndex = written.findIndex((data) =>
data.includes('main recovery was unavailable')
)
const combinedLive = liveChunk.repeat(chunkCount)
const liveWrites = written.filter((data) => data === combinedLive)
expect(warningIndex).toBeGreaterThanOrEqual(0)
expect(liveWrites).toHaveLength(1)
expect(written.indexOf(combinedLive)).toBeGreaterThan(warningIndex)
disposable.dispose()
expect(vi.getTimerCount()).toBe(0)
})
it('keeps foreground output when hidden-backlog snapshot recovery is unavailable', async () => {
const pendingTimeouts: {
canceled: boolean
delay: number
fn: () => void
id: number
}[] = []
const originalSetTimeout = globalThis.setTimeout
const originalClearTimeout = globalThis.clearTimeout
let nextTimeoutId = 1
globalThis.setTimeout = vi.fn((fn: () => void, delay?: number) => {
const timeout = {
canceled: false,
delay: typeof delay === 'number' ? delay : 0,
fn,
id: nextTimeoutId++
}
pendingTimeouts.push(timeout)
return timeout.id as unknown as ReturnType<typeof setTimeout>
}) as unknown as typeof setTimeout
globalThis.clearTimeout = vi.fn((id: ReturnType<typeof setTimeout>) => {
const numericId = id as unknown as number
const timeout = pendingTimeouts.find((candidate) => candidate.id === numericId)
if (timeout) {
timeout.canceled = true
}
}) as unknown as typeof clearTimeout
const runNextTimeoutWithDelay = async (delay: number): Promise<void> => {
const index = pendingTimeouts.findIndex(
(timeout) => !timeout.canceled && timeout.delay === delay
)
expect(index).toBeGreaterThanOrEqual(0)
const [timeout] = pendingTimeouts.splice(index, 1)
timeout.fn()
await flushAsyncTicks(20)
}
const drainTimeoutsWithDelay = async (delay: number): Promise<void> => {
while (pendingTimeouts.some((timeout) => !timeout.canceled && timeout.delay === delay)) {
await runNextTimeoutWithDelay(delay)
}
}
let disposable: { dispose: () => void } | null = null
try {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks7 = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
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
>
getMainBufferSnapshot.mockResolvedValue(null)
const hidden = 'x'.repeat(2 * 1024 * 1024 + 1)
const live = 'foreground-after-unavailable\r\n'
const pane = createPane(1)
const manager = createManager(1)
const deps = createDeps({
isVisibleRef: { current: false }
})
disposable = connectPanePty(
pane as never,
manager as never,
bindPaneAgentTitleMocks(deps as never, agentTitleMocks7)
)
await flushAsyncTicks(6)
capturedDataCallback.current?.(hidden, { seq: hidden.length, rawLength: hidden.length })
;(deps.isVisibleRef as { current: boolean }).current = true
capturedDataCallback.current?.(live, {
seq: hidden.length + live.length,
rawLength: live.length
})
await flushAsyncTicks(20)
expect(getMainBufferSnapshot).toHaveBeenCalledTimes(1)
expect(pane.terminal.write).not.toHaveBeenCalledWith(
expect.stringContaining(live),
expect.any(Function)
)
await runNextTimeoutWithDelay(50)
await runNextTimeoutWithDelay(50)
await runNextTimeoutWithDelay(50)
await drainTimeoutsWithDelay(0)
expect(getMainBufferSnapshot).toHaveBeenCalledTimes(4)
expect(pane.terminal.write).toHaveBeenCalledWith(
expect.stringContaining(
'Orca skipped hidden terminal output because main recovery was unavailable.'
),
expect.any(Function)
)
expect(pane.terminal.write).toHaveBeenCalledWith(
expect.stringContaining(live),
expect.any(Function)
)
} finally {
disposable?.dispose()
globalThis.setTimeout = originalSetTimeout
globalThis.clearTimeout = originalClearTimeout
}
})
it('keeps a newer same-PTY hidden restore after a timed-out snapshot resolves late', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks8 = await vi.importMock<PtyConnectionAgentTitleMocks>(
@@ -26,150 +26,6 @@ import {
} from '../test-fixtures/pty-connection-test-environment'
import { mockPanePtyArgs } from '../test-fixtures/pty-connection-test-connect-mock-pane'
export function registerPtyConnectionForegroundBulkAndInteractiveOutputTests(): void {
let mockStoreState: StoreState
let transportFactoryQueue: MockTransport[] = []
let createdTransportOptions: Record<string, unknown>[] = []
let storeSubscribers: ((state: StoreState) => void)[] = []
function createDeps(overrides: Record<string, unknown> = {}) {
return buildPaneConnectionDeps(() => mockStoreState, overrides)
}
describe('connectPanePty', () => {
beforeEach(async () => {
vi.clearAllMocks()
transportFactoryQueue = []
createdTransportOptions = []
storeSubscribers = []
mockStoreState = createInitialStoreState(() => mockStoreState)
bindPtyConnectionTestMocks({
getState: () => mockStoreState,
getTransportQueue: () => transportFactoryQueue,
getTransportOptions: () => createdTransportOptions,
getStoreSubscribers: () => storeSubscribers,
setStoreSubscribers: (subscribers) => {
storeSubscribers = subscribers
}
})
await installTerminalTestGlobals()
})
afterEach(async () => {
await restoreTerminalTestGlobals()
})
it('queues visible bulk output off the synchronous xterm write path', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
const pane = createPane(1)
const transport = createMockTransport('pty-1')
const capturedDataCallback: { current: ((data: string) => void) | null } = { current: null }
transport.connect.mockImplementation(
async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
capturedDataCallback.current = callbacks.onData ?? null
return 'pty-1'
}
)
transportFactoryQueue.push(transport)
connectPanePty(
...mockPanePtyArgs(
pane,
createManager(1),
bindPaneAgentTitleMocks(createDeps(), agentTitleMocks)
)
)
await flushAsyncTicks()
expect(capturedDataCallback.current).not.toBeNull()
vi.useFakeTimers()
capturedDataCallback.current?.('x'.repeat(16 * 1024))
expect(pane.terminal.write).not.toHaveBeenCalled()
vi.advanceTimersByTime(0)
expect(pane.terminal.write).toHaveBeenCalledWith('x'.repeat(16 * 1024), expect.any(Function))
})
it('keeps ANSI redraws after terminal input on the immediate xterm write path', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks2 = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
const pane = createPane(1)
const transport = createMockTransport('pty-1')
const capturedDataCallback: { current: ((data: string) => void) | null } = { current: null }
transport.connect.mockImplementation(
async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
capturedDataCallback.current = callbacks.onData ?? null
return 'pty-1'
}
)
transportFactoryQueue.push(transport)
connectPanePty(
...mockPanePtyArgs(
pane,
createManager(1),
bindPaneAgentTitleMocks(createDeps(), agentTitleMocks2)
)
)
await flushAsyncTicks()
const onDataMock = pane.terminal.onData as unknown as {
mock: { calls: [[(data: string) => void] | []] }
}
const terminalInputHandler = onDataMock.mock.calls[0]?.[0]
expect(terminalInputHandler).toBeTypeOf('function')
terminalInputHandler?.('a')
const redraw = `\x1b[2J\x1b[H${'codex composer redraw '.repeat(200)}`
capturedDataCallback.current?.(redraw)
expect(pane.terminal.write).toHaveBeenCalledWith(redraw, expect.any(Function))
})
it('keeps large ANSI redraws after terminal input on the immediate xterm write path', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks3 = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
const pane = createPane(1)
const transport = createMockTransport('pty-1')
const capturedDataCallback: { current: ((data: string) => void) | null } = { current: null }
transport.connect.mockImplementation(
async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
capturedDataCallback.current = callbacks.onData ?? null
return 'pty-1'
}
)
transportFactoryQueue.push(transport)
connectPanePty(
...mockPanePtyArgs(
pane,
createManager(1),
bindPaneAgentTitleMocks(createDeps(), agentTitleMocks3)
)
)
await flushAsyncTicks()
const onDataMock = pane.terminal.onData as unknown as {
mock: { calls: [[(data: string) => void] | []] }
}
const terminalInputHandler = onDataMock.mock.calls[0]?.[0]
expect(terminalInputHandler).toBeTypeOf('function')
terminalInputHandler?.('a')
const redraw = `\x1b[2J\x1b[H${'codex large composer redraw '.repeat(1_200)}`
expect(redraw.length).toBeGreaterThan(16 * 1024)
expect(redraw.length).toBeLessThan(128 * 1024)
capturedDataCallback.current?.(redraw)
expect(pane.terminal.write).toHaveBeenCalledWith(redraw, expect.any(Function))
})
})
}
export function registerPtyConnectionForegroundFrameSchedulingTests(): void {
let mockStoreState: StoreState
let transportFactoryQueue: MockTransport[] = []
@@ -202,194 +58,6 @@ export function registerPtyConnectionForegroundFrameSchedulingTests(): void {
await restoreTerminalTestGlobals()
})
it('keeps large ANSI redraws after captured shortcut input on the immediate path', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks6 = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
const pane = createPane(1)
const transport = createMockTransport('pty-1')
const capturedDataCallback: { current: ((data: string) => void) | null } = { current: null }
transport.connect.mockImplementation(
async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
capturedDataCallback.current = callbacks.onData ?? null
return 'pty-1'
}
)
transportFactoryQueue.push(transport)
const binding = connectPanePty(
...mockPanePtyArgs(
pane,
createManager(1),
bindPaneAgentTitleMocks(createDeps(), agentTitleMocks6)
)
)
await flushAsyncTicks()
binding.markShortcutTerminalInputSent()
const redraw = `\x1b[2J\x1b[H${'pi composer redraw '.repeat(200)}`
expect(redraw.length).toBeGreaterThan(2_048)
capturedDataCallback.current?.(redraw)
expect(pane.terminal.write).toHaveBeenCalledWith(redraw, expect.any(Function))
})
it('does not let OpenTUI-style small ANSI redraw bursts monopolize foreground writes', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks7 = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
const pane = createPane(1)
const transport = createMockTransport('pty-1')
const capturedDataCallback: { current: ((data: string) => void) | null } = { current: null }
transport.connect.mockImplementation(
async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
capturedDataCallback.current = callbacks.onData ?? null
return 'pty-1'
}
)
transportFactoryQueue.push(transport)
connectPanePty(
...mockPanePtyArgs(
pane,
createManager(1),
bindPaneAgentTitleMocks(createDeps(), agentTitleMocks7)
)
)
await flushAsyncTicks()
expect(capturedDataCallback.current).not.toBeNull()
vi.useFakeTimers()
const frames = Array.from({ length: 270 }, (_, index) =>
[
'\x1b[?2026h',
'\x1b[?25l',
`\x1b[2;3H\x1b[38;2;255;138;0m${index % 2 === 0 ? '#' : '*'}${'*'.repeat(7)}\x1b[0m`,
`\x1b[2;12H\x1b[38;2;255;138;0mOpenTUI synthetic active TUI redraw ${index}\x1b[0m`,
`\x1b[4;6H\x1b[38;2;231;237;247m${'#'.repeat(36)} ${'opentui'.repeat(48)}\x1b[0m`
].join('')
)
expect(frames.every((frame) => frame.length <= 2048 && frame.includes('\x1b['))).toBe(true)
expect(frames.join('').length).toBeGreaterThan(128 * 1024)
for (const frame of frames) {
capturedDataCallback.current?.(frame)
}
expect(pane.terminal.write.mock.calls.length).toBeLessThan(frames.length)
vi.advanceTimersByTime(0)
expect(pane.terminal.write.mock.calls.length).toBeGreaterThan(0)
})
it('drains a post-submit synchronized frame on the fast path when its end marker arrives late', async () => {
// Submit-opened DEC 2026 frames keep the fast ~16ms drain even when ConPTY splits their close past 150ms (STA-1041).
const restoreNavigator = temporarilySetNavigatorUserAgent(
'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
)
await loadFreshTerminalModules()
try {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks8 = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
const transport = createMockTransport()
const capturedDataCallback: { current: ((data: string) => void) | null } = { current: null }
transport.connect.mockImplementation(
async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
capturedDataCallback.current = callbacks.onData ?? null
return 'pty-id'
}
)
transportFactoryQueue.push(transport)
const pane = createPane(1)
connectPanePty(
...mockPanePtyArgs(
pane,
createManager(1),
bindPaneAgentTitleMocks(createDeps(), agentTitleMocks8)
)
)
await flushAsyncTicks(6)
vi.useFakeTimers()
sendTerminalInputThroughPane(pane, '\r')
const repaintBody = 'opencode repaint '.repeat(200)
expect(repaintBody.length).toBeGreaterThan(2048)
capturedDataCallback.current?.(`\x1b[?2026h${repaintBody}`)
// The frame body holds, then its hold-safety fallback drains it.
vi.advanceTimersByTime(40)
pane.terminal.write.mockClear()
// ConPTY delivers the closing chunk well past the 150ms redraw window.
vi.advanceTimersByTime(300)
const endChunk = `${repaintBody}\x1b[?25l\x1b[13;14H\x1b[?25h\x1b[?2026l`
expect(endChunk.length).toBeGreaterThan(2048)
capturedDataCallback.current?.(endChunk)
vi.advanceTimersByTime(20)
expect(pane.terminal.write).toHaveBeenCalledTimes(1)
} finally {
vi.useRealTimers()
restoreNavigator()
}
})
it('keeps coalescing a synchronized frame end with no recent input behind the 1s fallback', async () => {
// Only keystrokes arm the fast path; background redraws retain the full fallback to hide transient Windows cursors (STA-1041).
const restoreNavigator = temporarilySetNavigatorUserAgent(
'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
)
await loadFreshTerminalModules()
try {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks9 = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
const transport = createMockTransport()
const capturedDataCallback: { current: ((data: string) => void) | null } = { current: null }
transport.connect.mockImplementation(
async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
capturedDataCallback.current = callbacks.onData ?? null
return 'pty-id'
}
)
transportFactoryQueue.push(transport)
const pane = createPane(1)
connectPanePty(
...mockPanePtyArgs(
pane,
createManager(1),
bindPaneAgentTitleMocks(createDeps(), agentTitleMocks9)
)
)
await flushAsyncTicks(6)
vi.useFakeTimers()
const repaintBody = 'opencode repaint '.repeat(200)
capturedDataCallback.current?.(`\x1b[?2026h${repaintBody}`)
// Let the frame body drain via its 250ms hold-safety fallback before isolating the closing chunk.
vi.advanceTimersByTime(300)
pane.terminal.write.mockClear()
const endChunk = `${repaintBody}\x1b[?25l\x1b[13;14H\x1b[?25h\x1b[?2026l`
capturedDataCallback.current?.(endChunk)
vi.advanceTimersByTime(20)
expect(pane.terminal.write).not.toHaveBeenCalled()
vi.advanceTimersByTime(1000)
expect(pane.terminal.write).toHaveBeenCalledTimes(1)
} finally {
vi.useRealTimers()
restoreNavigator()
}
})
it('clears the synchronized latch when ConPTY splits the frame end marker', async () => {
// Split DEC 2026 closes must release the foreground latch or later output freezes until blur (#8754).
const restoreNavigator = temporarilySetNavigatorUserAgent(
@@ -502,63 +170,5 @@ export function registerPtyConnectionForegroundFrameSchedulingTests(): void {
restoreNavigator()
}
})
it('coalesces a second synchronized frame that opens after the window with no keystroke', async () => {
// Why: the second frame opens after the interactive window, so its start is judged independently and stays on the 1s fallback.
const restoreNavigator = temporarilySetNavigatorUserAgent(
'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
)
await loadFreshTerminalModules()
try {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks12 = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
const transport = createMockTransport()
const capturedDataCallback: { current: ((data: string) => void) | null } = { current: null }
transport.connect.mockImplementation(
async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
capturedDataCallback.current = callbacks.onData ?? null
return 'pty-id'
}
)
transportFactoryQueue.push(transport)
const pane = createPane(1)
connectPanePty(
...mockPanePtyArgs(
pane,
createManager(1),
bindPaneAgentTitleMocks(createDeps(), agentTitleMocks12)
)
)
await flushAsyncTicks(6)
vi.useFakeTimers()
sendTerminalInputThroughPane(pane, '\r')
const repaintBody = 'opencode repaint '.repeat(200)
expect(repaintBody.length).toBeGreaterThan(2048)
capturedDataCallback.current?.(`\x1b[?2026h${repaintBody}\x1b[?2026l`)
vi.advanceTimersByTime(40)
// Move past the 400ms window with no further keystroke, then open frame 2.
vi.advanceTimersByTime(500)
capturedDataCallback.current?.(`\x1b[?2026h${repaintBody}`)
vi.advanceTimersByTime(40)
pane.terminal.write.mockClear()
const secondEndChunk = `${repaintBody}\x1b[?25l\x1b[13;14H\x1b[?25h\x1b[?2026l`
capturedDataCallback.current?.(secondEndChunk)
vi.advanceTimersByTime(20)
expect(pane.terminal.write).not.toHaveBeenCalled()
vi.advanceTimersByTime(1000)
expect(pane.terminal.write).toHaveBeenCalledTimes(1)
} finally {
vi.useRealTimers()
restoreNavigator()
}
})
})
}
@@ -1,619 +0,0 @@
import {
bindsPaneInStore,
expectWindowStartAskedMain,
mainStartsPaneAs,
expectNoWindowStart,
mainLayoutApplyTransport
} from '../test-fixtures/pty-connection-test-main-pane-start-fixtures'
import {
LEAF_2,
LEAF_1,
createMockTransport,
createPane,
createManager,
type MockTransport
} from '../test-fixtures/pty-connection-test-pane-fixtures'
import { bindPtyConnectionTestMocks } from '../test-fixtures/pty-connection-mocks.spec'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { makePaneKey } from '../../../../../../shared/workspace-layout/stable-pane-id'
import { ORCA_LAYOUT_OWNER_IS_MAIN } from '../../../../../../shared/layout-owner-switch'
import { flushAsyncTicks } from '../test-fixtures/pty-connection-test-async'
import { UUID_RE } from '../test-fixtures/pty-connection-test-constants'
import {
buildPaneConnectionDeps,
type PtyConnectionAgentTitleMocks,
bindPaneAgentTitleMocks
} from '../test-fixtures/pty-connection-test-deps'
import { createInitialStoreState } from '../test-fixtures/pty-connection-test-store-fixtures'
import type { StoreState } from '../test-fixtures/pty-connection-test-store-state'
import {
installTerminalTestGlobals,
restoreTerminalTestGlobals
} from '../test-fixtures/pty-connection-test-environment'
export function registerPtyConnectionSleepingRecordResumeTests(): void {
let mockStoreState: StoreState
let transportFactoryQueue: MockTransport[] = []
let createdTransportOptions: Record<string, unknown>[] = []
let storeSubscribers: ((state: StoreState) => void)[] = []
function createDeps(overrides: Record<string, unknown> = {}) {
return buildPaneConnectionDeps(() => mockStoreState, overrides)
}
describe('connectPanePty', () => {
beforeEach(async () => {
vi.clearAllMocks()
transportFactoryQueue = []
createdTransportOptions = []
storeSubscribers = []
mockStoreState = createInitialStoreState(() => mockStoreState)
bindPtyConnectionTestMocks({
getState: () => mockStoreState,
getTransportQueue: () => transportFactoryQueue,
getTransportOptions: () => createdTransportOptions,
getStoreSubscribers: () => storeSubscribers,
setStoreSubscribers: (subscribers) => {
storeSubscribers = subscribers
}
})
await installTerminalTestGlobals()
;(await mainLayoutApplyTransport()).reset()
})
afterEach(async () => {
await restoreTerminalTestGlobals()
})
it('uses sleeping-record launch config for pane cold restore after settings change', async () => {
vi.useFakeTimers({ toFake: ['Date', 'setTimeout', 'clearTimeout'] })
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
const transport = createMockTransport('fresh-pty')
transport.connect.mockImplementation(async ({ sessionId }: { sessionId?: string }) => {
if (sessionId) {
return {
id: 'fresh-pty',
coldRestore: { scrollback: 'cold-payload', cwd: '/tmp/wt-1' }
}
}
return 'fresh-pty'
})
transportFactoryQueue.push(transport)
const paneKey = makePaneKey('tab-1', LEAF_1)
const launchConfig = {
agentCommand: "codex '--model' 'gpt-5' '--reasoning-effort' 'high'",
agentArgs: '--model gpt-5 --reasoning-effort high',
agentEnv: {
CODEX_PROFILE: 'captured',
ORCA_PANE_KEY: 'wrong-pane',
ORCA_TAB_ID: 'wrong-tab',
ORCA_WORKTREE_ID: 'wrong-worktree',
ORCA_WORKSPACE_ID: 'wrong-workspace'
}
}
mockStoreState = {
...mockStoreState,
tabsByWorktree: {
'wt-1': [{ id: 'tab-1', ptyId: 'lost-pty' }]
},
settings: {
...mockStoreState.settings,
agentCmdOverrides: {},
agentDefaultArgs: { codex: '--model changed' },
agentDefaultEnv: { codex: { CODEX_PROFILE: 'changed' } }
},
agentStatusByPaneKey: {},
sleepingAgentSessionsByPaneKey: {
[paneKey]: {
paneKey,
tabId: 'tab-1',
worktreeId: 'wt-1',
agent: 'codex',
providerSession: { key: 'session_id', id: 'codex-session-1' },
prompt: 'finish the task',
state: 'working',
capturedAt: 1,
updatedAt: 1,
launchConfig
}
}
} as StoreState
const pane = createPane(1)
const manager = createManager(1)
const deps = createDeps({
restoredLeafId: LEAF_1,
restoredPtyIdByLeafId: { [LEAF_1]: 'lost-pty' }
})
connectPanePty(
pane as never,
manager as never,
bindPaneAgentTitleMocks(deps as never, agentTitleMocks)
)
await flushAsyncTicks(20)
await vi.advanceTimersByTimeAsync(70)
if (ORCA_LAYOUT_OWNER_IS_MAIN) {
// Main resumes the agent; the window only attaches to the cold-restored terminal and paints it.
expect(transport.connect).toHaveBeenCalledWith(
expect.objectContaining({ sessionId: 'lost-pty', attachOnlyView: true })
)
expectNoWindowStart(transport, mockStoreState)
expect(transport.sendInput).not.toHaveBeenCalled()
return
}
const reattachArgs = transport.connect.mock.calls.find(
([args]) => args.sessionId === 'lost-pty'
)?.[0]
const launchToken = (reattachArgs?.env as Record<string, string> | undefined)
?.ORCA_AGENT_LAUNCH_TOKEN
expect(transport.sendInput).not.toHaveBeenCalled()
expect(launchToken).toMatch(new RegExp(`^${UUID_RE}$`))
expect(transport.connect).toHaveBeenCalledWith(
expect.objectContaining({
sessionId: 'lost-pty',
command:
"codex '--model' 'gpt-5' '--reasoning-effort' 'high' '-c' 'disable_paste_burst=true' 'resume' 'codex-session-1'",
env: expect.objectContaining({
CODEX_PROFILE: 'captured',
ORCA_PANE_KEY: paneKey,
ORCA_TAB_ID: 'tab-1',
ORCA_WORKTREE_ID: 'wt-1',
ORCA_WORKSPACE_ID: 'wt-1',
ORCA_AGENT_LAUNCH_TOKEN: launchToken
})
})
)
expect(mockStoreState.registerAgentLaunchConfig).toHaveBeenCalledWith(paneKey, launchConfig, {
agentType: 'codex',
launchToken,
tabId: 'tab-1',
leafId: LEAF_1
})
})
})
}
export function registerPtyConnectionLiveResumeIdentityTests(): void {
let mockStoreState: StoreState
let transportFactoryQueue: MockTransport[] = []
let createdTransportOptions: Record<string, unknown>[] = []
let storeSubscribers: ((state: StoreState) => void)[] = []
function createDeps(overrides: Record<string, unknown> = {}) {
return buildPaneConnectionDeps(() => mockStoreState, overrides)
}
/** Main's frame binding tab-1's pane 2. */
const bindLeaf2 = bindsPaneInStore(() => mockStoreState, 'tab-1', LEAF_2)
describe('connectPanePty', () => {
beforeEach(async () => {
vi.clearAllMocks()
transportFactoryQueue = []
createdTransportOptions = []
storeSubscribers = []
mockStoreState = createInitialStoreState(() => mockStoreState)
bindPtyConnectionTestMocks({
getState: () => mockStoreState,
getTransportQueue: () => transportFactoryQueue,
getTransportOptions: () => createdTransportOptions,
getStoreSubscribers: () => storeSubscribers,
setStoreSubscribers: (subscribers) => {
storeSubscribers = subscribers
}
})
await installTerminalTestGlobals()
;(await mainLayoutApplyTransport()).reset()
})
afterEach(async () => {
await restoreTerminalTestGlobals()
})
it('prefers live-entry launch config for pane cold restore when status survived PTY loss', async () => {
vi.useFakeTimers({ toFake: ['Date', 'setTimeout', 'clearTimeout'] })
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks5 = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
const transport = createMockTransport('fresh-pty')
transport.connect.mockImplementation(async ({ sessionId }: { sessionId?: string }) => {
if (sessionId) {
return {
id: 'fresh-pty',
coldRestore: { scrollback: 'cold-payload', cwd: '/tmp/wt-1' }
}
}
return 'fresh-pty'
})
transportFactoryQueue.push(transport)
const paneKey = makePaneKey('tab-1', LEAF_1)
const launchConfig = {
agentCommand: "codex '--model' 'gpt-5-mini'",
agentArgs: '--model gpt-5-mini',
agentEnv: {}
}
mockStoreState = {
...mockStoreState,
tabsByWorktree: {
'wt-1': [{ id: 'tab-1', ptyId: 'lost-pty' }]
},
settings: {
...mockStoreState.settings,
agentCmdOverrides: {},
agentDefaultArgs: { codex: '--model changed' }
},
agentStatusByPaneKey: {
[paneKey]: {
paneKey,
state: 'working',
prompt: 'finish the task',
agentType: 'codex',
providerSession: { key: 'session_id', id: 'codex-session-1' }
}
},
agentLaunchConfigByPaneKey: {
[paneKey]: { launchConfig }
},
sleepingAgentSessionsByPaneKey: {}
} as StoreState
const pane = createPane(1)
const manager = createManager(1)
const deps = createDeps({
restoredLeafId: LEAF_1,
restoredPtyIdByLeafId: { [LEAF_1]: 'lost-pty' }
})
connectPanePty(
pane as never,
manager as never,
bindPaneAgentTitleMocks(deps as never, agentTitleMocks5)
)
await flushAsyncTicks(20)
await vi.advanceTimersByTimeAsync(70)
if (ORCA_LAYOUT_OWNER_IS_MAIN) {
// Main resumes from the live entry; the window only attaches and paints.
expect(transport.connect).toHaveBeenCalledWith(
expect.objectContaining({ sessionId: 'lost-pty', attachOnlyView: true })
)
expectNoWindowStart(transport, mockStoreState)
expect(transport.sendInput).not.toHaveBeenCalled()
return
}
expect(transport.sendInput).not.toHaveBeenCalled()
expect(transport.connect).toHaveBeenCalledWith(
expect.objectContaining({
sessionId: 'lost-pty',
command:
"codex '--model' 'gpt-5-mini' '-c' 'disable_paste_burst=true' 'resume' 'codex-session-1'",
env: expect.objectContaining({
ORCA_AGENT_LAUNCH_TOKEN: expect.stringMatching(new RegExp(`^${UUID_RE}$`))
})
})
)
expect(mockStoreState.registerAgentLaunchConfig).toHaveBeenCalledWith(paneKey, launchConfig, {
agentType: 'codex',
launchToken: expect.stringMatching(new RegExp(`^${UUID_RE}$`)),
tabId: 'tab-1',
leafId: LEAF_1
})
expect(mockStoreState.clearSleepingAgentSession).not.toHaveBeenCalled()
})
it.each(['ordinary', 'settled-worker'])(
'restores %s through main with one resume command',
async (kind) => {
vi.useFakeTimers({ toFake: ['Date', 'setTimeout', 'clearTimeout'] })
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks6 = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
const retainedPtyId = 'wt-1@@lost-pty'
const transport = createMockTransport()
transport.connect.mockImplementation(async ({ sessionId }: { sessionId?: string }) =>
sessionId
? {
id: 'fresh-pty',
coldRestore: { scrollback: 'cold-payload', cwd: '/tmp/wt-1' }
}
: 'fresh-pty'
)
transportFactoryQueue.push(transport)
const paneKey = makePaneKey('tab-1', LEAF_1)
mockStoreState = {
...mockStoreState,
tabsByWorktree: {
'wt-1': [{ id: 'tab-1', ptyId: retainedPtyId }]
},
settings: {
...mockStoreState.settings,
agentCmdOverrides: {}
},
agentStatusByPaneKey: {
[paneKey]: {
paneKey,
state: 'working',
prompt: 'finish the task',
agentType: 'claude',
providerSession: { key: 'session_id', id: 'claude-session-1' }
}
},
sleepingAgentSessionsByPaneKey: {
[paneKey]: {
paneKey,
tabId: 'tab-1',
worktreeId: 'wt-1',
agent: 'claude',
providerSession: { key: 'session_id', id: 'claude-session-1' },
prompt: 'finish the task',
state: 'working',
capturedAt: 1,
updatedAt: 1,
...(kind === 'settled-worker'
? { automaticResumeBlockedBy: 'legacy-orchestration-worker' }
: {})
}
}
} as StoreState
connectPanePty(
createPane(1) as never,
createManager(1) as never,
bindPaneAgentTitleMocks(
createDeps({
restoredLeafId: LEAF_1,
restoredPtyIdByLeafId: { [LEAF_1]: retainedPtyId }
}) as never,
agentTitleMocks6
)
)
await flushAsyncTicks(20)
await vi.advanceTimersByTimeAsync(70)
if (ORCA_LAYOUT_OWNER_IS_MAIN) {
// Main's start carries the one resume; the window's one connect is an attach.
expect(transport.connect).toHaveBeenCalledTimes(1)
expect(transport.connect).toHaveBeenCalledWith(
expect.objectContaining({ sessionId: retainedPtyId, attachOnlyView: true })
)
expectNoWindowStart(transport, mockStoreState)
expect(mockStoreState.tabsByWorktree['wt-1']).toHaveLength(1)
return
}
expect(transport.connect).toHaveBeenCalledTimes(1)
expect(transport.attach).not.toHaveBeenCalled()
const options = transport.connect.mock.calls[0]?.[0] as {
sessionId?: string
command?: string
}
expect(options.sessionId).toBe(retainedPtyId)
expect(options.command).toContain('claude-session-1')
expect(options.command?.match(/--resume/g)).toHaveLength(1)
expect(mockStoreState.tabsByWorktree['wt-1']).toHaveLength(1)
}
)
it('ignores stale live launch config when cold restore identity lookup rejects it', async () => {
vi.useFakeTimers({ toFake: ['Date', 'setTimeout', 'clearTimeout'] })
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks7 = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
const transport = createMockTransport('fresh-pty')
transport.connect.mockImplementation(async ({ sessionId }: { sessionId?: string }) => {
if (sessionId) {
return {
id: 'fresh-pty',
coldRestore: { scrollback: 'cold-payload', cwd: '/tmp/wt-1' }
}
}
return 'fresh-pty'
})
transportFactoryQueue.push(transport)
const paneKey = makePaneKey('tab-1', LEAF_1)
mockStoreState = {
...mockStoreState,
tabsByWorktree: {
'wt-1': [{ id: 'tab-1', ptyId: 'lost-pty' }]
},
settings: {
...mockStoreState.settings,
agentCmdOverrides: {},
agentDefaultArgs: { codex: '--model current' }
},
agentStatusByPaneKey: {
[paneKey]: {
paneKey,
state: 'working',
prompt: 'finish the task',
agentType: 'codex',
providerSession: { key: 'session_id', id: 'codex-session-1' }
}
},
agentLaunchConfigByPaneKey: {
[paneKey]: {
launchConfig: {
agentCommand: "codex '--model' 'stale'",
agentArgs: '--model stale',
agentEnv: {}
}
}
},
getAgentLaunchConfigForStatusEntry: vi.fn(() => undefined),
sleepingAgentSessionsByPaneKey: {
[paneKey]: {
paneKey,
tabId: 'tab-1',
worktreeId: 'wt-1',
agent: 'codex',
providerSession: { key: 'session_id', id: 'older-codex-session' },
prompt: 'older task',
state: 'working',
capturedAt: 1,
updatedAt: 1,
launchConfig: {
agentCommand: "codex '--model' 'sleeping-stale'",
agentArgs: '--model sleeping-stale',
agentEnv: { CODEX_PROFILE: 'sleeping-stale' }
}
}
}
} as StoreState
const pane = createPane(1)
const manager = createManager(1)
const deps = createDeps({
restoredLeafId: LEAF_1,
restoredPtyIdByLeafId: { [LEAF_1]: 'lost-pty' }
})
connectPanePty(
pane as never,
manager as never,
bindPaneAgentTitleMocks(deps as never, agentTitleMocks7)
)
await flushAsyncTicks(20)
await vi.advanceTimersByTimeAsync(70)
if (ORCA_LAYOUT_OWNER_IS_MAIN) {
// Main picks the resume recipe; the window neither builds nor registers one.
expect(transport.connect).toHaveBeenCalledWith(
expect.objectContaining({ sessionId: 'lost-pty', attachOnlyView: true })
)
expectNoWindowStart(transport, mockStoreState)
return
}
expect(mockStoreState.getAgentLaunchConfigForStatusEntry).toHaveBeenCalledWith(
expect.objectContaining({ paneKey, agentType: 'codex' })
)
expect(transport.connect).toHaveBeenCalledWith(
expect.objectContaining({
sessionId: 'lost-pty',
// Neither stale recipe applies, and settings defaults never stand in for a recorded one.
command: "codex '-c' 'disable_paste_burst=true' 'resume' 'codex-session-1'"
})
)
expect(mockStoreState.registerAgentLaunchConfig).toHaveBeenCalledWith(
paneKey,
expect.objectContaining({
agentArgs: ''
}),
expect.objectContaining({
agentType: 'codex',
tabId: 'tab-1',
leafId: LEAF_1
})
)
})
it('shows the restored banner when a sleeping resume falls back to a fresh shell', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks8 = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
const staleSessionId = 'wt-1@@stale-session'
const transport = createMockTransport()
transport.connect.mockImplementation(async (opts: { sessionId?: string }) => {
if (opts.sessionId) {
return undefined
}
const onPtySpawn = createdTransportOptions[0]?.onPtySpawn as
| ((ptyId: string) => void)
| undefined
onPtySpawn?.('fresh-pty')
return 'fresh-pty'
})
transportFactoryQueue.push(transport)
const paneKey = makePaneKey('tab-1', LEAF_2)
mockStoreState = {
...mockStoreState,
tabsByWorktree: {
'wt-1': [{ id: 'tab-1', ptyId: staleSessionId }]
},
ptyIdsByTabId: {
'tab-1': [staleSessionId]
},
terminalLayoutsByTabId: {
'tab-1': {
root: { type: 'leaf', leafId: LEAF_2 },
activeLeafId: LEAF_2,
expandedLeafId: null,
ptyIdsByLeafId: { [LEAF_2]: staleSessionId }
}
},
settings: {
...mockStoreState.settings,
agentCmdOverrides: {}
},
agentStatusByPaneKey: {},
sleepingAgentSessionsByPaneKey: {
[paneKey]: {
paneKey,
tabId: 'tab-1',
worktreeId: 'wt-1',
agent: 'codex',
providerSession: { key: 'session_id', id: 'codex-session-1' },
prompt: 'finish the task',
state: 'working',
capturedAt: 1,
updatedAt: 1
}
}
} as StoreState
const pane = createPane(2)
const manager = createManager(2)
const deps = createDeps({
restoredLeafId: LEAF_2,
restoredPtyIdByLeafId: { [LEAF_2]: staleSessionId }
})
await mainStartsPaneAs({ ptyId: 'pty-main', bind: bindLeaf2, transport })
connectPanePty(
pane as never,
manager as never,
bindPaneAgentTitleMocks(deps as never, agentTitleMocks8)
)
await flushAsyncTicks(10)
expect(transport.connect).toHaveBeenNthCalledWith(
1,
expect.objectContaining({ sessionId: staleSessionId })
)
if (ORCA_LAYOUT_OWNER_IS_MAIN) {
// A missing reattach asks main to start and preserve the resume record; the window cannot spawn its own shell.
await expectWindowStartAskedMain(transport, paneKey, mockStoreState)
expect(transport.connect).toHaveBeenLastCalledWith(
expect.objectContaining({ sessionId: 'pty-main', attachOnlyView: true })
)
expect(mockStoreState.clearSleepingAgentSession).not.toHaveBeenCalled()
expect(deps.onPtyErrorRef.current).not.toHaveBeenCalled()
return
}
expect(transport.connect).toHaveBeenNthCalledWith(
2,
expect.not.objectContaining({ sessionId: expect.any(String) })
)
expect(deps.clearTabPtyId).toHaveBeenCalledWith('tab-1', staleSessionId)
expect(pane.terminal.write).not.toHaveBeenCalledWith(
expect.stringContaining('--- session restored ---'),
expect.any(Function)
)
expect(deps.onShowSessionRestoredBanner).toHaveBeenCalledTimes(1)
expect(deps.onShowSessionRestoredBanner).toHaveBeenCalledWith(2, 'restored')
expect(mockStoreState.sleepingAgentSessionsByPaneKey[paneKey]).toMatchObject({
paneKey,
origin: 'quit',
restoreOnTabOpenOnly: true
})
})
})
}
@@ -1,133 +0,0 @@
// @vitest-environment happy-dom
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { registerAgentStatusStartupSnapshot } from '@/hooks/ipc-events/agent-status-startup-snapshot'
import { isPaneResumingInPlace } from '@/lib/pane-spawn-deferral-registry'
import { bindDeferredColdRestoreAndSnapshot } from '../deferred-cold-restore-and-snapshot'
import { startDeferredSessionReattach } from '../reattach/deferred-session-reattach-connect'
import type { ConnectPanePtySession } from '../connect-pane-pty-session'
import type { ColdRestoreAgentResumeStartup } from './fresh-spawn-types'
vi.mock('../../../../../../shared/layout-owner-switch', () => ({ ORCA_LAYOUT_OWNER_IS_MAIN: true }))
vi.mock('../../layout/reconciliation/terminal-lifecycle-diagnostics', () => ({
warnTerminalLifecycleAnomaly: vi.fn()
}))
vi.mock('../instrumentation/pty-connect-diagnostics', () => ({
recordPtyConnectDiagnostic: vi.fn()
}))
vi.mock('../../pty-transport/host-ownership/ssh-session-gone-verdict', () => ({
isSshSessionGoneError: () => false
}))
const RECORD_PANE_KEY = 'tab:leaf'
function resumeStartup(): ColdRestoreAgentResumeStartup {
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the starts read only the command, agent, launch config and the record's pane key.
return {
command: 'claude --resume c1',
agent: 'claude',
launchConfig: { agentCommand: 'claude', agentArgs: '', agentEnv: {} },
launchToken: 'token-1',
sleepingRecordEntry: { paneKey: RECORD_PANE_KEY, record: {} }
} as unknown as ColdRestoreAgentResumeStartup
}
function deferred<T>(): { promise: Promise<T>; resolve: (value: T) => void } {
let resolve: (value: T) => void = () => {}
const promise = new Promise<T>((res) => {
resolve = res
})
return { promise, resolve }
}
/** A paired server's pane, which the window still starts itself; main starts every other one. */
const PAIRED_SERVER = 'env-paired'
function createSession(connect: () => Promise<unknown>, runtimeEnvironmentId: string | null) {
const transport = { connect: vi.fn(connect), getPtyId: () => null }
const fixture = {
pane: { id: 1, leafId: 'leaf' },
transport,
disposed: false,
transportStreamGeneration: 1,
runtimeEnvironmentId,
deps: {
tabId: 'tab',
worktreeId: 'wt',
paneTransportsRef: { current: new Map([[1, transport]]) },
clearTabPtyId: vi.fn()
},
buildColdRestoreAgentResumeStartup: resumeStartup,
captureTransportOutputCallbacks: () => ({ generation: 1, callbacks: {} }),
beginReattachLiveDataDeferral: vi.fn(),
finishReattachLiveDataDeferral: vi.fn(),
shouldDeclareHiddenAtSpawn: () => false,
mergeStartupEnvWithPaneIdentity: (env: Record<string, string>) => env,
handleReattachResult: vi.fn().mockResolvedValue(true),
clearExitedPanePtyLayoutBinding: vi.fn(),
startFreshColdRestoreAgentResume: vi.fn().mockResolvedValue('fresh-pty'),
reportError: vi.fn(),
cols: 80,
rows: 24,
cacheKey: RECORD_PANE_KEY
}
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the fixture provides every session field the two starts read.
return fixture as unknown as ConnectPanePtySession
}
describe('a start that resumes a record marks its pane as resuming in place (layout-owner build)', () => {
let bridge: ReturnType<typeof registerAgentStatusStartupSnapshot>
beforeEach(() => {
bridge = registerAgentStatusStartupSnapshot()
bridge.settle()
Object.defineProperty(window, 'api', {
configurable: true,
value: { pty: { declarePendingPaneSerializer: vi.fn().mockResolvedValue(null) } }
})
})
afterEach(() => bridge.dispose())
it('the reattach that carries the resume holds the mark until it settles', async () => {
const connected = deferred<unknown>()
const session = createSession(() => connected.promise, PAIRED_SERVER)
startDeferredSessionReattach(session, 'restored-pty')
await vi.waitFor(() => expect(session.transport.connect).toHaveBeenCalled())
expect(isPaneResumingInPlace(RECORD_PANE_KEY)).toBe(true)
connected.resolve({ id: 'restored-pty' })
await vi.waitFor(() => expect(isPaneResumingInPlace(RECORD_PANE_KEY)).toBe(false))
})
it('the fresh start that resumes the record holds the mark until it settles', async () => {
const session = createSession(() => Promise.resolve(null), PAIRED_SERVER)
bindDeferredColdRestoreAndSnapshot(session)
const spawned = deferred<string | null>()
session.startFreshSpawn = vi.fn(() => spawned.promise)
const start = session.spawnColdRestoreAgentResume(resumeStartup())
expect(isPaneResumingInPlace(RECORD_PANE_KEY)).toBe(true)
spawned.resolve('fresh-pty')
await start
await vi.waitFor(() => expect(isPaneResumingInPlace(RECORD_PANE_KEY)).toBe(false))
})
it('a pane main starts: its reattach is a view that carries no resume and holds no mark', async () => {
const connected = deferred<unknown>()
const session = createSession(() => connected.promise, null)
startDeferredSessionReattach(session, 'restored-pty')
await vi.waitFor(() => expect(session.transport.connect).toHaveBeenCalled())
expect(session.transport.connect).toHaveBeenCalledWith(
expect.objectContaining({ sessionId: 'restored-pty', attachOnlyView: true })
)
expect(session.transport.connect).toHaveBeenCalledWith(
expect.not.objectContaining({ command: expect.anything() })
)
expect(isPaneResumingInPlace(RECORD_PANE_KEY)).toBe(false)
connected.resolve({ id: 'restored-pty' })
await vi.waitFor(() => expect(session.handleReattachResult).toHaveBeenCalled())
expect(isPaneResumingInPlace(RECORD_PANE_KEY)).toBe(false)
})
})
@@ -73,23 +73,6 @@ describe("the window's banner for main's start of a pane", () => {
vi.unstubAllGlobals()
})
it('shows the outcome of the terminal the view attached, once per start', async () => {
const showMainPaneResumeOutcome = await loadBannerModule()
store.publish({
terminalPaneResumeOutcomesByPaneKey: {
[PANE_KEY]: { ptyId: 'pty-1', ptyGeneration: 1, outcome: 'restored' }
}
})
const first = attachedView()
showMainPaneResumeOutcome(first.session, 'pty-1')
expect(first.view.showSessionRestoredBanner).toHaveBeenCalledExactlyOnceWith('restored')
// A remounted view attaching the same start says nothing again.
const remounted = attachedView()
showMainPaneResumeOutcome(remounted.session, 'pty-1')
expect(remounted.view.showSessionRestoredBanner).not.toHaveBeenCalled()
})
it('shows once per start: a later start under the same terminal id shows again', async () => {
const showMainPaneResumeOutcome = await loadBannerModule()
const publish = (ptyGeneration: number, outcome: TerminalPaneResumeOutcome['outcome']) =>
@@ -127,21 +110,4 @@ describe("the window's banner for main's start of a pane", () => {
;(await loadBannerModule())(reloaded.session, 'pty-1')
expect(reloaded.view.showSessionRestoredBanner).not.toHaveBeenCalled()
})
it('shows nothing for an outcome naming another terminal', async () => {
const showMainPaneResumeOutcome = await loadBannerModule()
store.publish({
terminalPaneResumeOutcomesByPaneKey: {
[PANE_KEY]: { ptyId: 'pty-old', ptyGeneration: 1, outcome: 'restored' }
}
})
const { view, session } = attachedView()
showMainPaneResumeOutcome(session, 'pty-1')
store.publish({
terminalPaneResumeOutcomesByPaneKey: {
[PANE_KEY]: { ptyId: 'pty-old', ptyGeneration: 2, outcome: 'resume-unavailable' }
}
})
expect(view.showSessionRestoredBanner).not.toHaveBeenCalled()
})
})
@@ -1,236 +0,0 @@
import type * as React from 'react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { flushAsyncTicks } from '../test-fixtures/pty-connection-test-async'
import {
createMockTransport,
createPane,
createManager,
type MockTransport
} from '../test-fixtures/pty-connection-test-pane-fixtures'
import {
buildPaneConnectionDeps,
type PtyConnectionAgentTitleMocks,
bindPaneAgentTitleMocks
} from '../test-fixtures/pty-connection-test-deps'
import { createInitialStoreState } from '../test-fixtures/pty-connection-test-store-fixtures'
import type { StoreState } from '../test-fixtures/pty-connection-test-store-state'
import {
installTerminalTestGlobals,
restoreTerminalTestGlobals
} from '../test-fixtures/pty-connection-test-environment'
const {
resetAndRefreshAllTerminalWebglAtlases,
scheduleRuntimeGraphSync,
shouldSeedCacheTimerOnInitialTitle,
toastInfo,
notifyCodexPaneBoundForStaleSweep
} = vi.hoisted(() => ({
resetAndRefreshAllTerminalWebglAtlases: vi.fn(),
scheduleRuntimeGraphSync: vi.fn(),
shouldSeedCacheTimerOnInitialTitle: vi.fn(() => false),
toastInfo: vi.fn(),
notifyCodexPaneBoundForStaleSweep: vi.fn()
}))
let mockStoreState: StoreState
let transportFactoryQueue: MockTransport[] = []
let createdTransportOptions: Record<string, unknown>[] = []
let storeSubscribers: ((state: StoreState) => void)[] = []
vi.mock('@/runtime/sync-runtime-graph/graph-sync-scheduler', () => ({ scheduleRuntimeGraphSync }))
vi.mock('@/lib/pane-manager/pane-manager-registry', async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
resetAndRefreshAllTerminalWebglAtlases
}))
vi.mock('@/store', () => ({
useAppStore: {
getState: () => mockStoreState,
subscribe: (listener: (state: StoreState) => void) => {
storeSubscribers.push(listener)
return () => {
storeSubscribers = storeSubscribers.filter((candidate) => candidate !== listener)
}
}
}
}))
vi.mock('@/lib/agent-status/aggregation/worktree-agent-status', async (importOriginal) => {
const { buildAgentStatusModuleMock } =
await import('../test-fixtures/pty-connection-test-environment')
return buildAgentStatusModuleMock(await importOriginal<Record<string, unknown>>())
})
vi.mock('../../titles/cache-timer-seeding', () => ({ shouldSeedCacheTimerOnInitialTitle }))
vi.mock('sonner', () => ({ toast: { info: toastInfo } }))
vi.mock('@/lib/agent-session/accounts/codex/codex-stale-pane-sweep', () => ({
notifyCodexPaneBoundForStaleSweep
}))
vi.mock('react', async (importOriginal) => {
const actual = await importOriginal<typeof React>()
return {
...actual,
useCallback: <T extends (...args: unknown[]) => unknown>(fn: T): T => fn
}
})
vi.mock('../../pty-transport/ipc/pty-transport', () => ({
createIpcPtyTransport: vi.fn((options: Record<string, unknown>) => {
createdTransportOptions.push(options)
const nextTransport = transportFactoryQueue.shift()
if (!nextTransport) {
throw new Error('No mock transport queued')
}
return nextTransport
})
}))
vi.mock('../../pty-transport/remote-runtime/remote-runtime-pty-transport', () => ({
createRemoteRuntimePtyTransport: vi.fn(
(_environmentId: string, options: Record<string, unknown>) => {
createdTransportOptions.push(options)
const transport = transportFactoryQueue.shift()
if (!transport) {
throw new Error('No remote mock transport queued')
}
return transport
}
)
}))
vi.mock('../../pty-transport/delivery/pty-dispatcher', async (importOriginal) => {
const actual = await importOriginal<Record<string, unknown>>()
return { ...actual, getEagerPtyBufferHandle: vi.fn(() => undefined) }
})
function spawnOnConnect(transport: MockTransport, ptyId: string): void {
transport.connect.mockImplementation(async () => {
const onPtySpawn = createdTransportOptions[0]?.onPtySpawn as ((id: string) => void) | undefined
onPtySpawn?.(ptyId)
return ptyId
})
}
describe('connectPanePty queued startup consume', () => {
beforeEach(async () => {
vi.resetModules()
vi.clearAllMocks()
transportFactoryQueue = []
createdTransportOptions = []
storeSubscribers = []
mockStoreState = createInitialStoreState(() => mockStoreState)
await installTerminalTestGlobals()
})
afterEach(async () => {
await restoreTerminalTestGlobals()
vi.unstubAllGlobals()
})
it('spends the queued startup only after the pty is bound to the tab', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks2 = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
const transport = createMockTransport()
spawnOnConnect(transport, 'fresh-pty')
transportFactoryQueue.push(transport)
const callOrder: string[] = []
const deps = buildPaneConnectionDeps(() => mockStoreState, {
startup: { command: 'echo queued' },
updateTabPtyId: vi.fn(() => {
callOrder.push('updateTabPtyId')
}),
onQueuedStartupSpawned: vi.fn(() => {
callOrder.push('onQueuedStartupSpawned')
})
})
connectPanePty(
createPane(1) as never,
createManager(1) as never,
bindPaneAgentTitleMocks(deps as never, agentTitleMocks2)
)
await flushAsyncTicks(20)
expect(callOrder).toContain('onQueuedStartupSpawned')
expect(callOrder.indexOf('updateTabPtyId')).toBeGreaterThanOrEqual(0)
expect(callOrder.indexOf('updateTabPtyId')).toBeLessThan(
callOrder.indexOf('onQueuedStartupSpawned')
)
})
it('leaves the queued startup unspent when the spawn never reaches onPtySpawn', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks3 = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
const transport = createMockTransport()
// A retired/reattached spawn resolves without ever announcing a fresh pty.
transport.connect.mockImplementation(async () => undefined)
transportFactoryQueue.push(transport)
const onQueuedStartupSpawned = vi.fn()
const deps = buildPaneConnectionDeps(() => mockStoreState, {
startup: { command: 'echo queued' },
onQueuedStartupSpawned
})
connectPanePty(
createPane(1) as never,
createManager(1) as never,
bindPaneAgentTitleMocks(deps as never, agentTitleMocks3)
)
await flushAsyncTicks(20)
// The next mount must still find the command queued, or it is lost for good.
expect(onQueuedStartupSpawned).not.toHaveBeenCalled()
})
it('keeps a throwing consume from escaping into the spawn', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks4 = await vi.importMock<PtyConnectionAgentTitleMocks>(
'@/lib/agent-status/aggregation/worktree-agent-status'
)
const transport = createMockTransport()
// onPtySpawn runs inside connect; callback throws would reject spawn and strand the pane, so cleanup must not throw.
let escapedIntoSpawn = false
transport.connect.mockImplementation(async () => {
const onPtySpawn = createdTransportOptions[0]?.onPtySpawn as
| ((id: string) => void)
| undefined
try {
onPtySpawn?.('fresh-pty')
} catch {
escapedIntoSpawn = true
return undefined
}
return 'fresh-pty'
})
transportFactoryQueue.push(transport)
const updateTabPtyId = vi.fn()
const deps = buildPaneConnectionDeps(() => mockStoreState, {
startup: { command: 'echo queued' },
updateTabPtyId,
onQueuedStartupSpawned: vi.fn(() => {
throw new Error('store blew up')
})
})
connectPanePty(
createPane(1) as never,
createManager(1) as never,
bindPaneAgentTitleMocks(deps as never, agentTitleMocks4)
)
await flushAsyncTicks(20)
expect(escapedIntoSpawn).toBe(false)
expect(updateTabPtyId).toHaveBeenCalled()
})
})
@@ -3,10 +3,7 @@ import { registerPtyConnectionForegroundAgentRoutingTests } from '../agent-ident
import { registerPtyConnectionForegroundAgentSamplingTests } from '../agent-identity/pty-connection-foreground-agent-sampling.spec'
import { registerPtyConnectionForegroundProcessExitTests } from '../agent-identity/pty-connection-foreground-process-exit.spec'
import { registerPtyConnectionForegroundRoutingConfirmationTests } from '../agent-identity/pty-connection-foreground-routing-confirmation.spec'
import {
registerPtyConnectionForegroundBulkAndInteractiveOutputTests,
registerPtyConnectionForegroundFrameSchedulingTests
} from '../output/pty-connection-foreground-write-path.spec'
import { registerPtyConnectionForegroundFrameSchedulingTests } from '../output/pty-connection-foreground-write-path.spec'
import { registerPtyConnectionInterruptInferenceTests } from '../input/pty-connection-interrupt-inference.spec'
import { registerPtyConnectionTypedAgentIdentityTests } from '../agent-identity/pty-connection-typed-agent-identity.spec'
@@ -24,7 +21,6 @@ describe(
registerPtyConnectionForegroundRoutingConfirmationTests
)
describe('pty-connection-foreground-write-path', () => {
registerPtyConnectionForegroundBulkAndInteractiveOutputTests()
registerPtyConnectionForegroundFrameSchedulingTests()
})
describe('pty-connection-interrupt-inference', registerPtyConnectionInterruptInferenceTests)
@@ -1,8 +1,4 @@
import { describe, vi } from 'vitest'
import {
registerPtyConnectionSleepingRecordResumeTests,
registerPtyConnectionLiveResumeIdentityTests
} from '../reattach/pty-connection-agent-session-resume.spec'
import {
registerPtyConnectionNonAgentLaunchRegistrationTests,
registerPtyConnectionUnboundAgentLaunchRegistrationTests
@@ -30,10 +26,8 @@ vi.mock('@/runtime/rpc/calls/runtime-rpc-call', async (importOriginal) =>
)
describe('pty-connection-agent-session-resume', () => {
registerPtyConnectionSleepingRecordResumeTests()
registerPtyConnectionNonAgentLaunchRegistrationTests()
registerPtyConnectionUnboundAgentLaunchRegistrationTests()
registerPtyConnectionLiveResumeIdentityTests()
})
describe(
'pty-connection-cold-restore-agent-resume',
@@ -62,23 +62,6 @@ afterEach(() => {
})
describe('delayed agent startup subscription', () => {
it('delivers when launch registration, PTY ownership, and layout binding arrive', () => {
seedPendingState()
const deliver = vi.fn().mockResolvedValue(undefined)
const startup = {} as never
queuePendingAgentStartupDelivery({
worktreeId: 'wt-background',
tabId: 'tab-background',
launchToken: 'target-launch',
startup,
deliver
})
bindPendingPty()
expect(deliver).toHaveBeenCalledWith('tab-background', 'pty-background', startup)
})
it('drops a delivery when its tab is removed before PTY binding', () => {
seedPendingState()
const deliver = vi.fn().mockResolvedValue(undefined)
@@ -2,7 +2,7 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { PluginHostListEntry } from '../../../../../shared/plugins/plugin-list-entry'
import { collectActivePluginCommands, usePluginPanelsStore } from './plugin-contribution-catalog'
import { usePluginPanelsStore } from './plugin-contribution-catalog'
function plugin(pluginKey: string): PluginHostListEntry {
return {
@@ -73,32 +73,3 @@ describe('plugin panel list loading', () => {
})
})
})
describe('plugin names on contributions', () => {
it("adds the publisher to a non-official plugin sharing an official plugin's name", () => {
const command = {
id: 'apply',
title: 'Apply',
context: 'global' as const,
handler: { type: 'worker' as const },
keybindings: []
}
const official = {
...plugin('stablyai.orca-catppuccin-theme'),
name: 'Catppuccin for Orca',
publisher: 'stablyai',
official: true,
commands: [command]
}
const community = {
...plugin('vale-c.catppuccin'),
name: 'Catppuccin for Orca',
publisher: 'vale-c',
commands: [command]
}
expect(collectActivePluginCommands([official, community]).map((c) => c.pluginName)).toEqual([
'Catppuccin for Orca',
'Catppuccin for Orca (vale-c)'
])
})
})