Reduce hidden terminal pressure during Codex typing (#2661)

* Reduce hidden terminal pressure during Codex typing

* Make terminal lag stress test tunable

* Add real Codex terminal stress mode
This commit is contained in:
Neil
2026-05-22 22:06:47 -07:00
committed by GitHub
parent e8837275fe
commit 69728c9904
5 changed files with 769 additions and 24 deletions
@@ -433,6 +433,101 @@ describe('connectPanePty', () => {
delete (globalThis as Record<string, unknown>).__ptyConnectDiag
})
it('coalesces same-class hidden title frames', async () => {
vi.useFakeTimers()
const { connectPanePty } = await import('./pty-connection')
transportFactoryQueue.push(createMockTransport('pty-1'))
const pane = createPane(1)
const manager = {
...createManager(1),
getActivePane: vi.fn(() => ({ id: 1 }))
}
const setRuntimePaneTitle = vi.fn((tabId: string, paneId: number, title: string) => {
mockStoreState.runtimePaneTitlesByTabId[tabId] = {
...mockStoreState.runtimePaneTitlesByTabId[tabId],
[paneId]: title
}
})
const deps = createDeps({
isVisibleRef: { current: false },
setRuntimePaneTitle
})
const binding = connectPanePty(pane as never, manager as never, deps as never)
const options = createdTransportOptions[0] as {
onTitleChange: (title: string, rawTitle: string) => void
}
options.onTitleChange('Codex working one', 'Codex working one')
options.onTitleChange('Codex working two', 'Codex working two')
expect(setRuntimePaneTitle).toHaveBeenCalledTimes(1)
vi.advanceTimersByTime(99)
expect(setRuntimePaneTitle).toHaveBeenCalledTimes(1)
vi.advanceTimersByTime(1)
expect(setRuntimePaneTitle).toHaveBeenCalledTimes(2)
expect(setRuntimePaneTitle).toHaveBeenLastCalledWith('tab-1', 1, 'Codex working two')
binding.dispose()
})
it('coalesces same-state hidden agent status pings', async () => {
vi.useFakeTimers()
const { connectPanePty } = await import('./pty-connection')
transportFactoryQueue.push(createMockTransport('pty-1'))
const deps = createDeps({ isVisibleRef: { current: false } })
const binding = connectPanePty(createPane(1) as never, createManager(1) as never, deps as never)
const options = createdTransportOptions[0] as {
onAgentStatus: (payload: { state: 'working'; prompt: string; toolInput?: string }) => void
}
options.onAgentStatus({ state: 'working', prompt: 'p', toolInput: 'first' })
options.onAgentStatus({ state: 'working', prompt: 'p', toolInput: 'second' })
expect(mockStoreState.setAgentStatus).toHaveBeenCalledTimes(1)
vi.advanceTimersByTime(100)
expect(mockStoreState.setAgentStatus).toHaveBeenCalledTimes(2)
expect(mockStoreState.setAgentStatus).toHaveBeenLastCalledWith(
makePaneKey('tab-1', LEAF_1),
{ state: 'working', prompt: 'p', toolInput: 'second' },
undefined
)
binding.dispose()
})
it('keeps hidden agent status transitions immediate', async () => {
vi.useFakeTimers()
const { connectPanePty } = await import('./pty-connection')
transportFactoryQueue.push(createMockTransport('pty-1'))
const deps = createDeps({ isVisibleRef: { current: false } })
const binding = connectPanePty(createPane(1) as never, createManager(1) as never, deps as never)
const options = createdTransportOptions[0] as {
onAgentStatus: (payload: {
state: 'working' | 'waiting' | 'done'
prompt: string
toolInput?: string
}) => void
}
options.onAgentStatus({ state: 'working', prompt: 'p', toolInput: 'first' })
options.onAgentStatus({ state: 'waiting', prompt: 'p', toolInput: 'blocked' })
options.onAgentStatus({ state: 'done', prompt: 'p', toolInput: 'complete' })
expect(mockStoreState.setAgentStatus).toHaveBeenCalledTimes(3)
vi.advanceTimersByTime(100)
expect(mockStoreState.setAgentStatus).toHaveBeenCalledTimes(3)
expect(mockStoreState.setAgentStatus).toHaveBeenLastCalledWith(
makePaneKey('tab-1', LEAF_1),
{ state: 'done', prompt: 'p', toolInput: 'complete' },
undefined
)
binding.dispose()
})
it('does not retain PTY connect diagnostics unless e2e debug state is enabled', async () => {
const { connectPanePty } = await import('./pty-connection')
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
@@ -33,7 +33,10 @@ import { recordTerminalOutput } from '@/lib/pane-manager/pane-scroll'
import { makePaneKey } from '../../../../shared/stable-pane-id'
import { createTerminalCommandLifecycle } from './terminal-command-lifecycle'
import { e2eConfig } from '@/lib/e2e-config'
import type { AgentStatusEntry } from '../../../../shared/agent-status-types'
import type {
AgentStatusEntry,
ParsedAgentStatusPayload
} from '../../../../shared/agent-status-types'
import { isWebTerminalSurfaceTabId } from '@/runtime/web-terminal-surface-id'
import {
createAgentInterruptInference,
@@ -53,6 +56,7 @@ const PTY_CONNECT_DIAG_LIMIT = 200
const AGENT_TASK_COMPLETE_NOTIFICATION_GRACE_MS = 250
const AGENT_TASK_COMPLETE_NOTIFICATION_MAX_WAIT_MS = 1000
const AGENT_TASK_COMPLETE_NOTIFICATION_DETAIL_MAX_AGE_MS = 10_000
const HIDDEN_TERMINAL_METADATA_FLUSH_MS = 100
let codexRestartNoticePresenceSource: Record<
string,
{ previousAccountLabel: string; nextAccountLabel: string }
@@ -476,6 +480,9 @@ export function connectPanePty(
const onExit = (ptyId: string): void => {
agentCompletionCoordinator.dispose()
clearHiddenMetadataFlushTimer()
pendingHiddenTitle = null
pendingHiddenAgentStatus = null
deps.syncPanePtyLayoutBinding(pane.id, null)
deps.clearRuntimePaneTitle(deps.tabId, pane.id)
deps.clearTabPtyId(deps.tabId, ptyId)
@@ -514,8 +521,11 @@ export function connectPanePty(
// Claude launches also start idle, but they have no prompt cache yet.
let hasConsideredInitialCacheTimerSeed = false
let allowInitialIdleCacheSeed = false
let pendingHiddenTitle: { title: string; rawTitle: string } | null = null
let pendingHiddenAgentStatus: ParsedAgentStatusPayload | null = null
let hiddenMetadataFlushTimer: ReturnType<typeof setTimeout> | null = null
const onTitleChange = (title: string, rawTitle: string): void => {
const applyTitleChangeNow = (title: string, rawTitle: string): void => {
manager.setPaneGpuRendering(pane.id, !isGeminiTerminalTitle(rawTitle))
deps.setRuntimePaneTitle(deps.tabId, pane.id, title)
if (syncAgentTaskCompleteNotificationEnabled()) {
@@ -546,6 +556,75 @@ export function connectPanePty(
}
}
const applyAgentStatusNow = (payload: ParsedAgentStatusPayload): void => {
// Why: capture the store snapshot once so the title lookup and the
// setAgentStatus call observe the same state. Re-reading getState()
// between the two lines opens a brief window where the title could
// shift (OSC title update landing in between) and the status would be
// stored against a title that was never paired with it.
const currentState = useAppStore.getState()
const title = currentState.runtimePaneTitlesByTabId?.[deps.tabId]?.[pane.id]
currentState.setAgentStatus(cacheKey, payload, title)
if (syncAgentTaskCompleteNotificationEnabled()) {
agentCompletionCoordinator.observeHookStatus(payload)
}
}
const clearHiddenMetadataFlushTimer = (): void => {
if (hiddenMetadataFlushTimer !== null) {
clearTimeout(hiddenMetadataFlushTimer)
hiddenMetadataFlushTimer = null
}
}
const flushHiddenMetadata = (): void => {
clearHiddenMetadataFlushTimer()
const title = pendingHiddenTitle
const agentStatus = pendingHiddenAgentStatus
pendingHiddenTitle = null
pendingHiddenAgentStatus = null
if (title) {
applyTitleChangeNow(title.title, title.rawTitle)
}
if (agentStatus) {
applyAgentStatusNow(agentStatus)
}
}
const scheduleHiddenMetadataFlush = (): void => {
if (hiddenMetadataFlushTimer !== null) {
return
}
hiddenMetadataFlushTimer = setTimeout(() => {
hiddenMetadataFlushTimer = null
flushHiddenMetadata()
}, HIDDEN_TERMINAL_METADATA_FLUSH_MS)
}
const hiddenTitleNeedsImmediateApply = (title: string): boolean => {
const currentTitle = useAppStore.getState().runtimePaneTitlesByTabId?.[deps.tabId]?.[pane.id]
return detectAgentStatusFromTitle(currentTitle ?? '') !== detectAgentStatusFromTitle(title)
}
const hiddenAgentStatusNeedsImmediateApply = (payload: ParsedAgentStatusPayload): boolean => {
const existing = useAppStore.getState().agentStatusByPaneKey[cacheKey]
return !existing || existing.state !== payload.state || payload.state === 'done'
}
const onTitleChange = (title: string, rawTitle: string): void => {
if (deps.isVisibleRef.current || hiddenTitleNeedsImmediateApply(title)) {
flushHiddenMetadata()
applyTitleChangeNow(title, rawTitle)
return
}
// Why: hidden Codex panes can emit decorative title frames faster than a
// user can observe them. Keep state transitions immediate, but coalesce
// same-class hidden frames so background agents don't steal the renderer
// thread from the focused xterm during typing.
pendingHiddenTitle = { title, rawTitle }
scheduleHiddenMetadataFlush()
}
const onPtySpawn = (ptyId: string): void => {
bindPanePtyId(pane.id, ptyId, deps.tabId)
pane.container.dataset.ptyId = ptyId
@@ -824,17 +903,13 @@ export function connectPanePty(
// Without this, the OSC parser in pty-transport strips sequences from xterm
// output but the status never reaches the store or dashboard/hover UI.
onAgentStatus: (payload) => {
// Why: capture the store snapshot once so the title lookup and the
// setAgentStatus call observe the same state. Re-reading getState()
// between the two lines opens a brief window where the title could
// shift (OSC title update landing in between) and the status would be
// stored against a title that was never paired with it.
const currentState = useAppStore.getState()
const title = currentState.runtimePaneTitlesByTabId?.[deps.tabId]?.[pane.id]
currentState.setAgentStatus(cacheKey, payload, title)
if (syncAgentTaskCompleteNotificationEnabled()) {
agentCompletionCoordinator.observeHookStatus(payload)
if (deps.isVisibleRef.current || hiddenAgentStatusNeedsImmediateApply(payload)) {
flushHiddenMetadata()
applyAgentStatusNow(payload)
return
}
pendingHiddenAgentStatus = payload
scheduleHiddenMetadataFlush()
}
}
const transport = runtimeEnvironmentId
@@ -1777,6 +1852,9 @@ export function connectPanePty(
pendingTerminalInputWrite = null
interruptInference.dispose()
clearTitleOnlyInterruptTimer()
clearHiddenMetadataFlushTimer()
pendingHiddenTitle = null
pendingHiddenAgentStatus = null
// Why: actively resolve any in-flight passphrase-gate waits so their
// zustand subscribers + async IIFEs don't hang for the rest of the
// session when the pane is torn down before SSH state changes.
@@ -36,7 +36,7 @@ describe('pane terminal output scheduler', () => {
writeTerminalOutput(terminal, 'b', { foreground: false })
expect(terminal.write).not.toHaveBeenCalled()
vi.advanceTimersByTime(50)
vi.advanceTimersByTime(100)
expect(terminal.write).toHaveBeenCalledTimes(1)
expect(terminal.write).toHaveBeenCalledWith('ab')
@@ -52,7 +52,7 @@ describe('pane terminal output scheduler', () => {
writeTerminalOutput(terminal, 'b', { foreground: false, beforeWrite })
expect(beforeWrite).not.toHaveBeenCalled()
vi.advanceTimersByTime(50)
vi.advanceTimersByTime(100)
expect(beforeWrite).toHaveBeenCalledTimes(1)
expect(beforeWrite).toHaveBeenCalledWith('ab')
@@ -84,12 +84,16 @@ describe('pane terminal output scheduler', () => {
writeTerminalOutput(terminal, `pane-${index}`, { foreground: false })
})
vi.advanceTimersByTime(50)
vi.advanceTimersByTime(100)
expect(terminals[0].write).toHaveBeenCalledWith('pane-0')
expect(terminals[1].write).not.toHaveBeenCalled()
expect(terminals[2].write).not.toHaveBeenCalled()
vi.advanceTimersByTime(50)
expect(terminals[1].write).toHaveBeenCalledWith('pane-1')
expect(terminals[2].write).not.toHaveBeenCalled()
vi.advanceTimersByTime(16)
vi.advanceTimersByTime(50)
expect(terminals[2].write).toHaveBeenCalledWith('pane-2')
})
@@ -103,16 +107,23 @@ describe('pane terminal output scheduler', () => {
writeTerminalOutput(terminals[1], 'pane-1', { foreground: false })
writeTerminalOutput(terminals[2], 'pane-2', { foreground: false })
vi.advanceTimersByTime(50)
vi.advanceTimersByTime(100)
expect(terminals[0].write).toHaveBeenCalledTimes(1)
expect(terminals[1].write).toHaveBeenCalledWith('pane-1')
expect(terminals[1].write).not.toHaveBeenCalled()
expect(terminals[2].write).not.toHaveBeenCalled()
// Why: a terminal with leftover bytes is deleted/re-set after each drain
// chunk, moving it to the back of the Map so a big burst cannot starve
// other queued panes.
vi.advanceTimersByTime(16)
vi.advanceTimersByTime(50)
expect(terminals[1].write).toHaveBeenCalledWith('pane-1')
expect(terminals[0].write).toHaveBeenCalledTimes(1)
vi.advanceTimersByTime(50)
expect(terminals[2].write).toHaveBeenCalledWith('pane-2')
expect(terminals[0].write).toHaveBeenCalledTimes(1)
vi.advanceTimersByTime(50)
expect(terminals[0].write).toHaveBeenCalledTimes(2)
})
@@ -134,7 +145,7 @@ describe('pane terminal output scheduler', () => {
writeTerminalOutput(terminal, 'stale', { foreground: false })
discardTerminalOutput(terminal)
vi.advanceTimersByTime(50)
vi.advanceTimersByTime(100)
expect(terminal.write).not.toHaveBeenCalled()
})
@@ -152,7 +163,7 @@ describe('pane terminal output scheduler', () => {
// Why: drain runs inside setTimeout; if the throw escapes drainQueuedOutput
// it would crash the timer callback and leave the scheduler poisoned.
expect(() => vi.advanceTimersByTime(50)).not.toThrow()
expect(() => vi.advanceTimersByTime(100)).not.toThrow()
expect(throwing.write).toHaveBeenCalledTimes(1)
// Advancing further must not rediscover the dead entry.
@@ -12,10 +12,10 @@ type QueueEntry = {
beforeWrite?: TerminalOutputBeforeWrite
}
const BACKGROUND_FLUSH_DELAY_MS = 50
const BACKGROUND_DRAIN_INTERVAL_MS = 16
const BACKGROUND_FLUSH_DELAY_MS = 100
const BACKGROUND_DRAIN_INTERVAL_MS = 50
const BACKGROUND_CHUNK_CHARS = 16 * 1024
const MAX_WRITES_PER_DRAIN = 2
const MAX_WRITES_PER_DRAIN = 1
const PARSE_SETTLE_TIMEOUT_MS = 250
const queuedByTerminal = new Map<TerminalOutputTarget, QueueEntry>()