diff --git a/src/renderer/src/lib/agent-paste-draft-echo-verification.test.ts b/src/renderer/src/lib/agent-paste-draft-echo-verification.test.ts index 38e865eaa98..707d3c0814f 100644 --- a/src/renderer/src/lib/agent-paste-draft-echo-verification.test.ts +++ b/src/renderer/src/lib/agent-paste-draft-echo-verification.test.ts @@ -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) diff --git a/src/renderer/src/lib/agent-paste-draft.ts b/src/renderer/src/lib/agent-paste-draft.ts index 76edc4811ae..f6174e1c5e5 100644 --- a/src/renderer/src/lib/agent-paste-draft.ts +++ b/src/renderer/src/lib/agent-paste-draft.ts @@ -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'