diff --git a/src/main/runtime/agent-prompt-submission-runtime.test.ts b/src/main/runtime/agent-prompt-submission-runtime.test.ts index 8b996b80874..711e887b5c5 100644 --- a/src/main/runtime/agent-prompt-submission-runtime.test.ts +++ b/src/main/runtime/agent-prompt-submission-runtime.test.ts @@ -676,8 +676,10 @@ describe('agent prompt submission runtime', () => { }) const rejected = expect(submission).rejects.toThrow('request_aborted') - // Why: the settle delay is platform-dependent (1_500 on Windows); a hardcoded 500 aborts before the Enter there. + // Why: the submit delay is 1_500 on Windows (ConPTY); a hardcoded 500 aborts before the Enter there. await vi.advanceTimersByTimeAsync(AGENT_PROMPT_SUBMIT_DELAY_MS) + // Why: pin the phase boundary so drift fails here instead of as an empty post-abort array. + expect(writes.filter((data) => data === '\r')).toHaveLength(1) controller.abort() await vi.runAllTimersAsync() diff --git a/src/main/runtime/agent-prompt-submission-windows-submit-delay.test.ts b/src/main/runtime/agent-prompt-submission-windows-submit-delay.test.ts new file mode 100644 index 00000000000..fdb90700de1 --- /dev/null +++ b/src/main/runtime/agent-prompt-submission-windows-submit-delay.test.ts @@ -0,0 +1,100 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import type * as AgentPromptInjection from '../../shared/agent-prompt-injection' +import { OrcaRuntimeService } from './orca-runtime' +import { makeStore } from './runtime-rpc-worktree-store-fixtures' + +// Why: vi.mock factories are hoisted above module-scope consts, so the delay must be hoisted too. +const { WINDOWS_SUBMIT_DELAY_MS } = vi.hoisted(() => ({ WINDOWS_SUBMIT_DELAY_MS: 1_500 })) +const WORKTREE_PATH = '/tmp/worktree-a' + +// Why: AGENT_PROMPT_SUBMIT_DELAY_MS is frozen from process.platform at import, so the 1_500 ConPTY +// path is otherwise unreachable on the Linux/macOS lanes that run this suite. +vi.mock('../../shared/agent-prompt-injection', async (importOriginal) => ({ + ...(await importOriginal()), + AGENT_PROMPT_SUBMIT_DELAY_MS: WINDOWS_SUBMIT_DELAY_MS +})) + +vi.mock('../git/worktree', () => ({ + listWorktrees: vi.fn().mockResolvedValue([ + { + path: '/tmp/worktree-a', + head: 'abc', + branch: 'feature/prompt-verification', + isBare: false, + isMainWorktree: false + } + ]), + listWorktreesStrict: vi.fn().mockResolvedValue([ + { + path: '/tmp/worktree-a', + head: 'abc', + branch: 'feature/prompt-verification', + isBare: false, + isMainWorktree: false + } + ]) +})) + +// Why: 'aider' is not a settlement agent, so submission takes the fixed-delay branch under test. +async function createPromptRuntime(): Promise<{ + runtime: OrcaRuntimeService + handle: string + writes: string[] +}> { + const runtime = new OrcaRuntimeService(makeStore() as never) + const writes: string[] = [] + runtime.setPtyController({ + spawn: vi.fn().mockResolvedValue({ id: 'pty-prompt' }), + write: (_ptyId, data) => { + writes.push(data) + return true + }, + kill: () => true, + getForegroundProcess: async () => null + }) + const terminal = await runtime.createTerminal(`path:${WORKTREE_PATH}`, { + launchAgent: 'aider' + }) + return { runtime, handle: terminal.handle, writes } +} + +function countSubmits(writes: readonly string[]): number { + return writes.filter((data) => data === '\r').length +} + +describe('agent prompt submission at the Windows submit delay', () => { + afterEach(() => vi.useRealTimers()) + + it('holds Enter until the full platform delay elapses', async () => { + vi.useFakeTimers() + const { runtime, handle, writes } = await createPromptRuntime() + const submission = runtime.sendTerminalAgentPrompt(handle, 'review this') + const stalled = expect(submission).rejects.toThrow('agent_prompt_stalled') + + await vi.advanceTimersByTimeAsync(WINDOWS_SUBMIT_DELAY_MS - 1) + expect(countSubmits(writes)).toBe(0) + await vi.advanceTimersByTimeAsync(1) + expect(countSubmits(writes)).toBe(1) + + await vi.runAllTimersAsync() + await stalled + }) + + it('does not send another Enter after cancellation during verification', async () => { + vi.useFakeTimers() + const controller = new AbortController() + const { runtime, handle, writes } = await createPromptRuntime() + const submission = runtime.sendTerminalAgentPrompt(handle, 'review this', { + signal: controller.signal + }) + const rejected = expect(submission).rejects.toThrow('request_aborted') + + await vi.advanceTimersByTimeAsync(WINDOWS_SUBMIT_DELAY_MS) + expect(countSubmits(writes)).toBe(1) + controller.abort() + await vi.runAllTimersAsync() + + await rejected + expect(countSubmits(writes)).toBe(1) + }) +})