fix(runtime): retire terminal snapshot probes before waiter timeout (#16149)

* refactor(renderer): use terminal paste sanitizer directly

* fix(runtime): order visible probe timeout settlement

* fix(runtime): skip unorderable visible probes
This commit is contained in:
Neil
2026-08-23 18:38:52 -07:00
committed by GitHub
parent 41ddcbde06
commit d9aa09cc8c
3 changed files with 23 additions and 14 deletions
+19 -5
View File
@@ -35600,23 +35600,36 @@ export class OrcaRuntimeService {
* later transition. A provider screen that is still working when this fires
* resolves through the poll, not here. */
private startTuiIdleVisibleReadProbe(waiter: TerminalWaiter, waiterTimeoutMs: number): void {
const snapshotTimeoutMs = Math.min(
VISIBLE_TERMINAL_SNAPSHOT_TIMEOUT_MS,
Math.max(0, waiterTimeoutMs - 1)
const settleMarginMs = Math.min(
TUI_IDLE_VISIBLE_PROBE_SETTLE_MARGIN_MS,
Math.max(1, Math.floor(waiterTimeoutMs / 3))
)
const probeTimeoutMs = Math.min(
VISIBLE_TERMINAL_SNAPSHOT_TIMEOUT_MS + settleMarginMs,
Math.max(0, waiterTimeoutMs - settleMarginMs)
)
const providerTimeoutMs = Math.min(
VISIBLE_TERMINAL_SNAPSHOT_TIMEOUT_MS,
Math.max(0, probeTimeoutMs - settleMarginMs)
)
// Node clamps sub-millisecond timers to 1ms, so no distinct retirement deadline exists.
if (providerTimeoutMs < 1) {
return
}
// Retire the provider before the detached probe and waiter can settle.
void withTimeout(
this.readTerminal(
waiter.handle,
{},
{
timeoutMs: snapshotTimeoutMs,
timeoutMs: providerTimeoutMs,
retireOnTimeout: true,
// Why: the ready banner stays in scrollback for the whole session, so
// classifying history would call a working agent idle (#15569 review).
visibleScreenOnly: true
}
),
snapshotTimeoutMs,
probeTimeoutMs,
null
)
.then((read) => {
@@ -38641,6 +38654,7 @@ const MAX_TERMINAL_PREVIEW_CHARS = 32 * 1024
export const AUTHORITATIVE_TERMINAL_SNAPSHOT_TIMEOUT_MS = 8_000
const VISIBLE_TERMINAL_SNAPSHOT_TIMEOUT_MS = 750
const VISIBLE_TERMINAL_SNAPSHOT_RETRY_MS = 1_000
const TUI_IDLE_VISIBLE_PROBE_SETTLE_MARGIN_MS = 10
const MAX_PREVIEW_LINES = 6
const MAX_PREVIEW_CHARS = 300
const WORKTREE_STATUS_PRIORITY: Record<RuntimeWorktreeStatus, number> = {
@@ -1,4 +1,5 @@
import type { RuntimeTerminalSend, RuntimeTerminalWait } from '../../../shared/runtime-types'
import { sanitizeTerminalPasteText } from '@/components/terminal-pane/terminal-bracketed-paste'
import { useAppStore } from '@/store'
import { callRuntimeRpc, getActiveRuntimeTarget } from '@/runtime/runtime-rpc-client'
import { getSettingsForWorktreeRuntimeOwner } from '@/lib/worktree-runtime-owner'
@@ -10,8 +11,7 @@ import {
import {
BRACKETED_PASTE_BEGIN,
BRACKETED_PASTE_END,
POST_PASTE_SUBMIT_DELAY_MS,
sanitizeBracketedPasteContent
POST_PASTE_SUBMIT_DELAY_MS
} from './agent-paste-draft'
import type { ActiveAgentNotesSendResult } from './active-agent-note-send-result'
import {
@@ -182,7 +182,7 @@ async function sendPromptWithGuardedPasteAndEnter(
return { status: initialAgentStatus.status }
}
const pastePayload = `${BRACKETED_PASTE_BEGIN}${sanitizeBracketedPasteContent(prompt)}${BRACKETED_PASTE_END}`
const pastePayload = `${BRACKETED_PASTE_BEGIN}${sanitizeTerminalPasteText(prompt)}${BRACKETED_PASTE_END}`
try {
const { send } = await callRuntimeRpc<{ send: RuntimeTerminalSend }>(
runtimeTarget,
+1 -6
View File
@@ -9,8 +9,7 @@ import {
} from '@/runtime/runtime-terminal-inspection'
import {
BRACKETED_PASTE_END,
BRACKETED_PASTE_START,
sanitizeTerminalPasteText
BRACKETED_PASTE_START
} from '@/components/terminal-pane/terminal-bracketed-paste'
import { runTerminalPtyInputTransaction } from '@/components/terminal-pane/terminal-pty-input-transaction'
import { waitForAgentReady } from './agent-ready-wait'
@@ -36,10 +35,6 @@ export const BRACKETED_PASTE_BEGIN = BRACKETED_PASTE_START
export { BRACKETED_PASTE_END }
export const POST_PASTE_SUBMIT_DELAY_MS = 50
export function sanitizeBracketedPasteContent(content: string): string {
return sanitizeTerminalPasteText(content)
}
// Why: "the tab has a PTY" and "the agent's composer accepts input" are separate
// states with separate failure modes, so they get separate budgets. A PTY that
// hasn't appeared in 8s means the launch itself failed — waiting the (longer)