mirror of
https://github.com/stablyai/orca.git
synced 2026-10-19 16:03:26 +00:00
refactor(terminal): make wedge probes explicit
This commit is contained in:
@@ -75,9 +75,9 @@ import {
|
||||
waitForTerminalReplayWritesParsed
|
||||
} from './replay-guard'
|
||||
import {
|
||||
armTerminalWriteStallWatch,
|
||||
isTerminalWritePipelineCertifiedDead,
|
||||
registerUndeliverableWriteHandler
|
||||
registerUndeliverableWriteHandler,
|
||||
requestTerminalWritePipelineProbe
|
||||
} from '@/lib/pane-manager/terminal-write-pipeline-health'
|
||||
import {
|
||||
captureTerminalPaneRecoveryGeneration,
|
||||
@@ -3358,6 +3358,8 @@ export function connectPanePty(
|
||||
const MAX_DEFERRED_REATTACH_LIVE_CHUNKS = 1_024
|
||||
const markTerminalInputSent = (): void => {
|
||||
lastTerminalInputAt = performance.now()
|
||||
// Why: input must probe a wedged xterm even when the PTY produces no renderer output.
|
||||
requestTerminalWritePipelineProbe(pane.terminal)
|
||||
}
|
||||
const recordTerminalInputForHibernation = (): void => {
|
||||
useAppStore.getState().recordTerminalInput(cacheKey)
|
||||
@@ -3605,10 +3607,6 @@ export function connectPanePty(
|
||||
// pre-existing session when a late reattach resolves, so a remount racing
|
||||
// a slow-but-alive connect costs a wasted view rebuild, not a shell.
|
||||
const TRANSPORT_CONNECT_SETTLE_GRACE_MS = 60_000
|
||||
// Why: user input must probe a wedged xterm even when no PTY output reaches the renderer.
|
||||
const armWritePipelineWatchForUserInput = (): void => {
|
||||
armTerminalWriteStallWatch(pane.terminal, { requireProbe: true })
|
||||
}
|
||||
const requestRecoveryForUndeliverableInput = (): void => {
|
||||
if (transport.isConnected?.() && transport.getPtyId() !== null) {
|
||||
return
|
||||
@@ -3734,7 +3732,6 @@ export function connectPanePty(
|
||||
}
|
||||
clearPendingTerminalInputIntent()
|
||||
markTerminalInputSent()
|
||||
armWritePipelineWatchForUserInput()
|
||||
const writePromise = transport
|
||||
.sendInputAccepted(data)
|
||||
.then((accepted) => {
|
||||
@@ -3760,7 +3757,6 @@ export function connectPanePty(
|
||||
claimViewportForUserActivity()
|
||||
if (transport.sendInput(data)) {
|
||||
markAcceptedTerminalInputSent()
|
||||
armWritePipelineWatchForUserInput()
|
||||
observeAcceptedShellCommandInput(data)
|
||||
observeAcceptedTerminalInput(data, intent)
|
||||
} else {
|
||||
@@ -3772,7 +3768,6 @@ export function connectPanePty(
|
||||
claimViewportForUserActivity()
|
||||
if (transport.sendInput(data)) {
|
||||
markAcceptedTerminalInputSent()
|
||||
armWritePipelineWatchForUserInput()
|
||||
observeAcceptedShellCommandInput(data)
|
||||
observeAcceptedTerminalInput(data)
|
||||
observeSentTerminalInputIntent(data)
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
isTerminalWritePipelineCertifiedDead,
|
||||
notifyUndeliverableWrite,
|
||||
registerUndeliverableWriteHandler,
|
||||
requestTerminalWritePipelineProbe,
|
||||
settleTerminalWriteStallWatch,
|
||||
WRITE_PIPELINE_STALL_CHECK_MS
|
||||
} from './terminal-write-pipeline-health'
|
||||
@@ -113,14 +114,15 @@ describe('terminal write pipeline health', () => {
|
||||
_resetWritePipelineHealthForTests(terminal)
|
||||
})
|
||||
|
||||
it('keeps a required probe armed through unrelated parse progress', () => {
|
||||
it('keeps a requested probe armed through unrelated parse progress', () => {
|
||||
vi.useFakeTimers()
|
||||
const terminal = makeTerminal()
|
||||
terminal.dropCallbacks = true
|
||||
const handler = vi.fn()
|
||||
registerUndeliverableWriteHandler(terminal, handler)
|
||||
|
||||
armTerminalWriteStallWatch(terminal, { requireProbe: true })
|
||||
armTerminalWriteStallWatch(terminal)
|
||||
requestTerminalWritePipelineProbe(terminal)
|
||||
settleTerminalWriteStallWatch(terminal)
|
||||
vi.advanceTimersByTime(WRITE_PIPELINE_STALL_CHECK_MS * 2)
|
||||
|
||||
|
||||
@@ -42,10 +42,12 @@ export function hasTerminalParseProgressSince(terminal: object, generation: numb
|
||||
return captureTerminalParseProgressGeneration(terminal) !== generation
|
||||
}
|
||||
|
||||
type StallWatchMode = 'output-completion' | 'fifo-probe'
|
||||
|
||||
type StallWatch = {
|
||||
timer: ReturnType<typeof setTimeout>
|
||||
onCertifiedDead?: () => void
|
||||
requireProbe: boolean
|
||||
mode: StallWatchMode
|
||||
}
|
||||
|
||||
const stallWatchByTerminal = new WeakMap<object, StallWatch>()
|
||||
@@ -111,27 +113,28 @@ export function isTerminalWritePipelineCertifiedDead(terminal: object): boolean
|
||||
* alive (slow parse), re-arm and keep waiting; probe silent for another
|
||||
* interval → dead, notify.
|
||||
*/
|
||||
export function armTerminalWriteStallWatch(
|
||||
function armTerminalWritePipelineWatch(
|
||||
terminal: WriteTarget,
|
||||
options: {
|
||||
mode: StallWatchMode
|
||||
onCertifiedDead?: () => void
|
||||
/** Ignore unrelated write completions so user-input health checks reach their FIFO probe. */
|
||||
requireProbe?: boolean
|
||||
stallCheckMs?: number
|
||||
} = {}
|
||||
}
|
||||
): void {
|
||||
if (certifiedDeadTerminals.has(terminal)) {
|
||||
return
|
||||
}
|
||||
const existingWatch = stallWatchByTerminal.get(terminal)
|
||||
if (existingWatch) {
|
||||
existingWatch.requireProbe ||= options.requireProbe === true
|
||||
if (options.mode === 'fifo-probe') {
|
||||
existingWatch.mode = 'fifo-probe'
|
||||
}
|
||||
return
|
||||
}
|
||||
const stallCheckMs = options.stallCheckMs ?? WRITE_PIPELINE_STALL_CHECK_MS
|
||||
const watch: StallWatch = {
|
||||
mode: options.mode,
|
||||
onCertifiedDead: options.onCertifiedDead,
|
||||
requireProbe: options.requireProbe === true,
|
||||
timer: setTimeout(probeForStall, stallCheckMs)
|
||||
}
|
||||
const certifyDead = (): void => certifyTerminalWritePipelineDead(terminal, watch)
|
||||
@@ -167,6 +170,21 @@ export function armTerminalWriteStallWatch(
|
||||
stallWatchByTerminal.set(terminal, watch)
|
||||
}
|
||||
|
||||
export function armTerminalWriteStallWatch(
|
||||
terminal: WriteTarget,
|
||||
options: { onCertifiedDead?: () => void; stallCheckMs?: number } = {}
|
||||
): void {
|
||||
armTerminalWritePipelineWatch(terminal, { ...options, mode: 'output-completion' })
|
||||
}
|
||||
|
||||
/** Require the active watch to reach its FIFO probe before it may settle. */
|
||||
export function requestTerminalWritePipelineProbe(
|
||||
terminal: WriteTarget,
|
||||
options: { stallCheckMs?: number } = {}
|
||||
): void {
|
||||
armTerminalWritePipelineWatch(terminal, { ...options, mode: 'fifo-probe' })
|
||||
}
|
||||
|
||||
/** Cancel a pending watch without claiming that any bytes parsed. */
|
||||
export function cancelTerminalWriteStallWatch(terminal: object): void {
|
||||
const watch = stallWatchByTerminal.get(terminal)
|
||||
@@ -177,10 +195,10 @@ export function cancelTerminalWriteStallWatch(terminal: object): void {
|
||||
stallWatchByTerminal.delete(terminal)
|
||||
}
|
||||
|
||||
/** Write completed normally — record progress and drop ordinary write watches. */
|
||||
/** Write completed normally — the pipeline is healthy; drop any pending watch. */
|
||||
export function settleTerminalWriteStallWatch(terminal: object): void {
|
||||
recordTerminalParseProgress(terminal)
|
||||
if (stallWatchByTerminal.get(terminal)?.requireProbe) {
|
||||
if (stallWatchByTerminal.get(terminal)?.mode === 'fifo-probe') {
|
||||
return
|
||||
}
|
||||
cancelTerminalWriteStallWatch(terminal)
|
||||
|
||||
Reference in New Issue
Block a user