fix(orchestration): re-press Enter when dsh's launchpad refuses a worker's first dispatch (#33499)

dsh worker dispatches (and other dsh agent-prompt sends) now get #33316's bounded Enter retry: dsh paints its launchpad before its session opens and refuses the first Enter with "still starting", so a fresh worker's task sat unsent. The retry only re-presses while the launchpad still holds the same prompt and stops once a turn starts.

Fixes #30778

Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>
This commit is contained in:
Neil
2026-10-25 03:36:06 -07:00
committed by GitHub
co-authored by Jinwoo Hong
parent 346e2ac98c
commit af99a031c5
4 changed files with 29 additions and 16 deletions
@@ -3,6 +3,7 @@ import { join } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { AGENT_PROMPT_BRACKETED_PASTE_END } from '../../shared/agent-prompt-injection'
import { createAgentPromptSubmissionRuntime } from './agent-prompt-submission-runtime-test-fixture'
import { dispatchPreambleSendOptions } from './orchestration/preamble'
vi.mock('../git/worktree', () => {
const worktrees = [
@@ -30,7 +31,7 @@ const DSH_TURN_STARTED = '\x1b]0;⠂ 🐋 \x07\x1b]7501;state=working:app=dsh-tu
describe('a dsh launch prompt whose first Enter the launchpad refused', () => {
afterEach(() => vi.useRealTimers())
async function launchDsh(screen: string, acceptingEnter: number) {
async function launchDsh(screen: string, acceptingEnter: number, dispatch = false) {
vi.useFakeTimers()
const { runtime, handle, writes } = await createAgentPromptSubmissionRuntime(
(runtime, data) => {
@@ -45,13 +46,19 @@ describe('a dsh launch prompt whose first Enter the launchpad refused', () => {
)
// The captures were painted on a 120x40 grid.
vi.spyOn(runtime, 'getTerminalSize').mockReturnValue({ cols: 120, rows: 40 })
const submission = runtime.sendTerminalAgentPrompt(handle, 'say hi in one word', {
inputKind: 'launch',
composerReady: true,
acceptQueued: true,
requestId: 'dsh-launch',
observationTimeoutMs: 0
})
const submission = runtime.sendTerminalAgentPrompt(
handle,
'say hi in one word',
dispatch
? dispatchPreambleSendOptions('dsh-dispatch')
: {
inputKind: 'launch',
composerReady: true,
acceptQueued: true,
requestId: 'dsh-launch',
observationTimeoutMs: 0
}
)
await vi.advanceTimersByTimeAsync(40_000)
await expect(submission).resolves.toMatchObject({ accepted: true })
return {
@@ -80,4 +87,12 @@ describe('a dsh launch prompt whose first Enter the launchpad refused', () => {
pastes: 1
})
})
// #30778: a fresh worker's tui-idle settles on dsh 0.17's boot `OSC 7501 idle`, before its session opens.
it('presses Enter again for a worker dispatch the launchpad refused', async () => {
await expect(launchDsh(fixture('launchpad-draft-starting'), 3, true)).resolves.toEqual({
enters: 3,
pastes: 1
})
})
})
@@ -182,8 +182,7 @@ export abstract class OrcaRuntimeWithWriteTerminalAgentPrompt extends OrcaRuntim
heldBudgetMs: getAgentPromptHeldSubmitBudgetMs(
writeHostPlatform,
this.getPtyAgent(ptyId),
pastePayload,
options.composerReady === true
pastePayload
)
}))
const effectTimeoutMs = resolveAgentPromptEffectTimeoutMs(this.getPtyAgent(ptyId))
+4 -5
View File
@@ -76,18 +76,17 @@ export function getMaxTerminalPasteBytesForIngestMs(
// Codex 0.162.1 on a low-spec Win11 host took 5.1 s for 1,849 chars and 21.1 s for 9,249 (~2.3 ms/char).
const WINDOWS_INPUT_RECORD_PASTE_MS_PER_CHAR = 3
/** How long a held prompt may still need Enter: an input-record TUI's paste burst, or a launch composer not yet submit-ready. */
/** How long a held prompt may still need Enter: an input-record TUI's paste burst, or a composer not yet submit-ready. */
export function getAgentPromptHeldSubmitBudgetMs(
platform: NodeJS.Platform,
agent: TerminalAgent | null | undefined,
pastePayload: string,
composerReady: boolean
pastePayload: string
): number {
if (!isTuiAgent(agent)) {
return 0
}
const config = TUI_AGENT_CONFIG[agent]
const launchMs = composerReady ? (config.submitRetryBudgetMs ?? 0) : 0
const startupMs = config.submitRetryBudgetMs ?? 0
const pasteBurstMs =
platform === 'win32' && config.windowsInputRecordPasteNewline
? pastePayload.length * WINDOWS_INPUT_RECORD_PASTE_MS_PER_CHAR
@@ -97,7 +96,7 @@ export function getAgentPromptHeldSubmitBudgetMs(
const capMs =
AGENT_PROMPT_EFFECT_TIMEOUT_MS -
2 * getTerminalPasteIngestMs(platform, Buffer.byteLength(pastePayload, 'utf8'))
return Math.max(0, Math.min(Math.max(launchMs, pasteBurstMs), capMs))
return Math.max(0, Math.min(Math.max(startupMs, pasteBurstMs), capMs))
}
// Codex dedupes a repeated placeholder label as `[Pasted Content N chars] #2`.
+1 -1
View File
@@ -64,7 +64,7 @@ export type TuiAgentConfig = {
composerReadyCaptures?: readonly string[]
/** Delay before one extra blind submit Enter, for agents that render their composer before Enter is live (codex); a no-op if the first Enter landed. */
submitRetryDelayMs?: number
/** How long a launch keeps re-pressing Enter at `submitRetryDelayMs` while its composer holds the prompt, for TUIs that paint it before their session can submit (dsh). */
/** How long an agent-prompt send keeps re-pressing Enter at `submitRetryDelayMs` while its composer holds the prompt, for TUIs that paint it before their session can submit (dsh). */
submitRetryBudgetMs?: number
/** Extra ms per logical prompt line before Enter, for TUIs that expand multiline paste slowly (antigravity). */
submitLineSettleMsPerLine?: number