fix(agents): widen echo window to cover agent cold boot

Readiness can fire on the shell's bracketed-paste handshake while the agent
binary is still booting; the paste sits in the TTY buffer and renders only
when the TUI drains stdin, several seconds later. Live-validated: claude
cold boot rendered the buffered paste after the 3s window, yielding a
false echo-timeout with the prompt sitting unsubmitted in the composer.
A slow verdict is safe (the Enter stays withheld until the echo), so the
window absorbs the boot instead.

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
brennanb2025
2026-07-08 15:34:15 -07:00
co-authored by Orca
parent bede7702b9
commit 1e7aa4a0da
2 changed files with 27 additions and 4 deletions
@@ -122,6 +122,26 @@ describe('pasteDraftWhenAgentReady echo verification', () => {
expect(onTimeout).not.toHaveBeenCalled()
})
it('verifies a late echo from an agent that was still booting when the paste landed', async () => {
const { promise, onTimeout } = startVerifiedPaste()
await flushMicrotasks()
testState.ptyObserver?.(`${DECSET_BRACKETED_PASTE}${CODEX_COMPOSER_PROMPT_RENDER}`)
await flushMicrotasks()
expect(testState.sendRuntimePtyInputVerified).toHaveBeenCalledTimes(1)
// Cold-booting agents drain the buffered paste seconds later; the echo
// must still verify and submit once it renders.
await vi.advanceTimersByTimeAsync(6000)
testState.ptyObserver?.(CODEX_VERBATIM_ECHO)
await flushMicrotasks()
await vi.advanceTimersByTimeAsync(50)
await expect(promise).resolves.toBe(true)
expect(testState.sendRuntimePtyInputVerified).toHaveBeenNthCalledWith(2, {}, 'pty-1', '\r')
expect(onTimeout).not.toHaveBeenCalled()
})
it('withholds the Enter and reports echo-timeout when the paste never renders', async () => {
const { promise, onTimeout } = startVerifiedPaste()
await flushMicrotasks()
@@ -132,7 +152,7 @@ describe('pasteDraftWhenAgentReady echo verification', () => {
// Trust/update screens only redraw; the pasted characters never render.
testState.ptyObserver?.(TRUST_SCREEN_REDRAW)
await vi.advanceTimersByTimeAsync(3000)
await vi.advanceTimersByTimeAsync(10_000)
await expect(promise).resolves.toBe(false)
expect(testState.sendRuntimePtyInputVerified).toHaveBeenCalledTimes(1)
+6 -3
View File
@@ -44,9 +44,12 @@ export function sanitizeBracketedPasteContent(content: string): string {
// or (2) the launch fails outright. The hard timeout caps the wait so a
// stuck launch doesn't pin a Promise forever.
const READINESS_TIMEOUT_MS = 8000
// Why: a live composer repaints the pasted text within one frame; the window
// only needs to absorb remote-PTY round-trips and slow first paints.
const ECHO_VERIFY_TIMEOUT_MS = 3000
// Why: readiness can fire on the shell's own bracketed-paste handshake while
// the agent binary is still booting — the paste then sits in the TTY buffer
// and only renders when the TUI drains stdin, several seconds later. The
// window must absorb that cold boot (plus remote-PTY round-trips); a slow
// verdict is safe because the submit Enter stays withheld until the echo.
const ECHO_VERIFY_TIMEOUT_MS = 10_000
export type PasteDeliveryTimeoutReason = 'readiness-timeout' | 'echo-timeout'