mirror of
https://github.com/stablyai/orca.git
synced 2026-09-25 16:02:38 +00:00
Window the retained-tail redraw path to the cursor's reach
Attribution (findings log 2026-07-03): main's onPtyData consumed ~93% of the event loop under an agent-TUI flood, and the dominant term was appendNormalizedToMultilineTailBuffer + finalizeRetainedTerminalRows materializing ~2x tail-length row objects plus a per-row trailing-space regex on every chunk — 0.888ms/chunk at the 2,000-line cap, on every Claude-Code-shaped frame (cursor-up + erase-below). The multiline algorithm now runs on a suffix window sized by the chunk's maximum upward cursor excursion (plus the inherited redraw cursor and a safety margin); the untouched prefix is shared by reference with a cheap last-char trailing-space check to match the reference trim. Pathological full-height cursor-ups fall back to the unwindowed implementation, which is kept verbatim and exported as the reference for the 500-case differential fuzz (retained-tail-redraw-window.equivalence.test.ts). Micro-bench at a full 2,000-line tail: 0.888 -> 0.073 ms/chunk (12x). 1,415 runtime tests green, typecheck clean. Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -22277,6 +22277,30 @@ function trimTerminalLineRight(line: string): string {
|
||||
return end === line.length ? line : line.slice(0, end)
|
||||
}
|
||||
|
||||
// Why a window: the unwindowed implementation below materializes a row object
|
||||
// per retained tail line and finalize re-allocates + regex-trims every row —
|
||||
// O(tail) per chunk (~0.9ms at the 2,000-line cap), measured at ~93% of the
|
||||
// main-process event loop under an agent-TUI flood (findings log 2026-07-03).
|
||||
// A redraw can only touch rows the cursor can reach, so run the algorithm on
|
||||
// a suffix window sized by the chunk's maximum upward cursor excursion and
|
||||
// share the untouched prefix by reference. Equality with the unwindowed
|
||||
// implementation is fuzz-verified in
|
||||
// retained-tail-redraw-window.equivalence.test.ts.
|
||||
const REDRAW_WINDOW_SAFETY_ROWS = 8
|
||||
|
||||
function maxUpwardCursorReach(
|
||||
normalizedChunk: string,
|
||||
previousRedrawCursor: RetainedTailRedrawCursor | null
|
||||
): number {
|
||||
let reach = previousRedrawCursor ? previousRedrawCursor.rowFromEnd : 0
|
||||
const cursorUpPattern = /\x1b\[(\d*)(?:;[\d;]*)?A/g
|
||||
let match: RegExpExecArray | null
|
||||
while ((match = cursorUpPattern.exec(normalizedChunk)) !== null) {
|
||||
reach += match[1] ? Number.parseInt(match[1], 10) : 1
|
||||
}
|
||||
return reach
|
||||
}
|
||||
|
||||
function appendNormalizedToMultilineTailBuffer(
|
||||
previousLines: string[],
|
||||
boundedPreviousPartialLine: string,
|
||||
@@ -22289,6 +22313,79 @@ function appendNormalizedToMultilineTailBuffer(
|
||||
redrawCursor: RetainedTailRedrawCursor | null
|
||||
truncated: boolean
|
||||
newCompleteLines: number
|
||||
} {
|
||||
const windowRows =
|
||||
maxUpwardCursorReach(normalizedChunk, previousRedrawCursor) + REDRAW_WINDOW_SAFETY_ROWS
|
||||
if (windowRows >= previousLines.length) {
|
||||
return appendNormalizedToMultilineTailBufferUnwindowed(
|
||||
previousLines,
|
||||
boundedPreviousPartialLine,
|
||||
normalizedChunk,
|
||||
previousPartialWasCapped,
|
||||
previousRedrawCursor
|
||||
)
|
||||
}
|
||||
const prefixLength = previousLines.length - windowRows
|
||||
const suffix = previousLines.slice(prefixLength)
|
||||
const windowed = appendNormalizedToMultilineTailBufferUnwindowed(
|
||||
suffix,
|
||||
boundedPreviousPartialLine,
|
||||
normalizedChunk,
|
||||
previousPartialWasCapped,
|
||||
previousRedrawCursor
|
||||
)
|
||||
let lines = previousLines.slice(0, prefixLength)
|
||||
// Why: the unwindowed finalize trims trailing spaces/tabs on every row; the
|
||||
// shared prefix must match without paying a regex per untouched row.
|
||||
for (let index = 0; index < lines.length; index += 1) {
|
||||
const line = lines[index]!
|
||||
const lastChar = line.charCodeAt(line.length - 1)
|
||||
if (lastChar === 32 || lastChar === 9) {
|
||||
lines[index] = line.replace(/[ \t]+$/g, '')
|
||||
}
|
||||
}
|
||||
for (const line of windowed.lines) {
|
||||
lines.push(line)
|
||||
}
|
||||
let truncated = windowed.truncated
|
||||
if (lines.length > MAX_TAIL_LINES) {
|
||||
lines = lines.slice(lines.length - MAX_TAIL_LINES)
|
||||
truncated = true
|
||||
}
|
||||
let totalChars = windowed.partialLine.length
|
||||
for (const line of lines) {
|
||||
totalChars += line.length
|
||||
}
|
||||
let dropCount = 0
|
||||
while (dropCount < lines.length && totalChars > MAX_TAIL_CHARS) {
|
||||
totalChars -= lines[dropCount]!.length
|
||||
dropCount += 1
|
||||
}
|
||||
if (dropCount > 0) {
|
||||
lines = lines.slice(dropCount)
|
||||
truncated = true
|
||||
}
|
||||
return {
|
||||
lines,
|
||||
partialLine: windowed.partialLine,
|
||||
redrawCursor: windowed.redrawCursor,
|
||||
truncated,
|
||||
newCompleteLines: windowed.newCompleteLines
|
||||
}
|
||||
}
|
||||
|
||||
export function appendNormalizedToMultilineTailBufferUnwindowed(
|
||||
previousLines: string[],
|
||||
boundedPreviousPartialLine: string,
|
||||
normalizedChunk: string,
|
||||
previousPartialWasCapped: boolean,
|
||||
previousRedrawCursor: RetainedTailRedrawCursor | null
|
||||
): {
|
||||
lines: string[]
|
||||
partialLine: string
|
||||
redrawCursor: RetainedTailRedrawCursor | null
|
||||
truncated: boolean
|
||||
newCompleteLines: number
|
||||
} {
|
||||
const rows: RetainedTerminalRow[] = [
|
||||
...previousLines.map((line) => ({ text: line, completed: true })),
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
appendNormalizedToTailBuffer,
|
||||
appendNormalizedToMultilineTailBufferUnwindowed
|
||||
} from './orca-runtime'
|
||||
|
||||
// Differential guard for the windowed redraw tail path: the public
|
||||
// appendNormalizedToTailBuffer routes vertical-control chunks through a
|
||||
// suffix-windowed wrapper (findings log 2026-07-03 — the unwindowed path was
|
||||
// O(tail) per chunk and dominated main's event loop under agent-TUI floods).
|
||||
// This fuzz asserts the windowed result is byte-identical to the reference
|
||||
// implementation across randomized tails and redraw chunks.
|
||||
|
||||
function mulberry32(seed: number): () => number {
|
||||
let a = seed >>> 0
|
||||
return () => {
|
||||
a = (a + 0x6d2b79f5) | 0
|
||||
let t = Math.imul(a ^ (a >>> 15), 1 | a)
|
||||
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t
|
||||
return ((t ^ (t >>> 14)) >>> 0) / 4294967296
|
||||
}
|
||||
}
|
||||
|
||||
function randomTail(rng: () => number, maxLines: number): string[] {
|
||||
const count = Math.floor(rng() * maxLines)
|
||||
return Array.from({ length: count }, (_, i) => {
|
||||
const base = `line ${i} ${'x'.repeat(Math.floor(rng() * 40))}`
|
||||
// Trailing whitespace included deliberately: the reference implementation
|
||||
// trims every row on each call, so the windowed prefix must match.
|
||||
return rng() < 0.3 ? `${base} ` : base
|
||||
})
|
||||
}
|
||||
|
||||
function randomRedrawChunk(rng: () => number): string {
|
||||
const parts: string[] = []
|
||||
const ops = 1 + Math.floor(rng() * 12)
|
||||
for (let i = 0; i < ops; i++) {
|
||||
const roll = rng()
|
||||
if (roll < 0.2) {
|
||||
parts.push(`\x1b[${1 + Math.floor(rng() * 12)}A`)
|
||||
} else if (roll < 0.3) {
|
||||
parts.push(`\x1b[${Math.floor(rng() * 3)}J`)
|
||||
} else if (roll < 0.4) {
|
||||
parts.push(`\x1b[${Math.floor(rng() * 3)}K`)
|
||||
} else if (roll < 0.5) {
|
||||
parts.push('\r')
|
||||
} else if (roll < 0.6) {
|
||||
parts.push(`\x1b[${1 + Math.floor(rng() * 30)}G`)
|
||||
} else if (roll < 0.7) {
|
||||
parts.push('\n')
|
||||
} else if (roll < 0.75) {
|
||||
parts.push('')
|
||||
} else {
|
||||
parts.push(`text${Math.floor(rng() * 100)} ${'y'.repeat(Math.floor(rng() * 20))}`)
|
||||
}
|
||||
}
|
||||
return parts.join('')
|
||||
}
|
||||
|
||||
describe('windowed redraw tail equivalence', () => {
|
||||
it('matches the unwindowed reference across 500 randomized cases', () => {
|
||||
const rng = mulberry32(42)
|
||||
for (let round = 0; round < 500; round++) {
|
||||
const tail = randomTail(rng, round % 5 === 0 ? 2100 : 300)
|
||||
const partial = rng() < 0.5 ? `partial ${'z'.repeat(Math.floor(rng() * 30))}` : ''
|
||||
const redrawCursor =
|
||||
rng() < 0.3 ? { rowFromEnd: Math.floor(rng() * 20), column: Math.floor(rng() * 40) } : null
|
||||
// Why the guaranteed cursor-up: the public function routes to the
|
||||
// multiline (windowed) path only for vertical-control chunks; chunks
|
||||
// without one take the single-line fast path, which is out of scope.
|
||||
const chunk = `\x1b[${1 + Math.floor(rng() * 4)}A${randomRedrawChunk(rng)}`
|
||||
|
||||
const actual = appendNormalizedToTailBuffer(tail, partial, chunk, redrawCursor)
|
||||
// Reference path over the full tail.
|
||||
const expected = appendNormalizedToMultilineTailBufferUnwindowed(
|
||||
tail,
|
||||
partial.slice(-4000),
|
||||
chunk,
|
||||
partial.length > 4000,
|
||||
redrawCursor
|
||||
)
|
||||
|
||||
expect(actual.lines, `round ${round} lines`).toEqual(expected.lines)
|
||||
expect(actual.partialLine, `round ${round} partial`).toBe(expected.partialLine)
|
||||
expect(actual.redrawCursor, `round ${round} cursor`).toEqual(expected.redrawCursor)
|
||||
expect(actual.truncated, `round ${round} truncated`).toBe(expected.truncated)
|
||||
expect(actual.newCompleteLines, `round ${round} newLines`).toBe(expected.newCompleteLines)
|
||||
}
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user