Files
orca/src/main/runtime/pty-transcript-prune-wait-cache.test.ts
T
NeilandOrca 61ecaaf521 perf(runtime): memoize onPtyData tail wait scan to halve per-chunk work (#7488)
* perf(runtime): memoize onPtyData tail wait scan to halve per-chunk work

onPtyData runs per raw PTY chunk (hundreds/sec during verbose builds and
agent token streaming). For any terminal past the 2000-line / 256KB tail
cap it built the full wait text (a map/trim/filter/join over the entire
retained tail) and lower-cased + scanned it twice per chunk — once for the
pre-append tail and once for the post-append tail — producing hundreds of
KB of transient string allocation per chunk and steady main-process GC/CPU
pressure under load.

Cache the post-append wait state (text + lower-cased blocked-signal scan)
on the pty/leaf record and reuse it as the next chunk's pre-append state.
The prior chunk's post-append tail *is* this chunk's pre-append tail, so
the cached scan is exact; reuse is gated on fromTail so the empty-tail
preview fallback (which depends on a value updated after append) is never
reused stale. This drops per-chunk full-tail scans from 2N to N+1.

Adds an equivalence test proving the memoized stamping is byte-for-byte
identical to the recompute-both-sides reference across split prompts,
partial lines, ready-after-blocked demotion, and tail eviction, plus a
count assertion (memoized N+1 vs reference 2N scans).

Co-authored-by: Orca <help@stably.ai>

* fix(runtime): clear memoized wait cache when a disconnected transcript is pruned

pruneDisconnectedPtyTranscript empties a disconnected PTY record's retained
tail but left the new tailWaitState memo untouched. If such a record resumed
output (adoption/reattach while a leaf keeps it alive), onPtyData would reuse
the stale pre-prune wait state (fromTail=true) as the next chunk's previous
state and could miss or mis-time the waitBlockedAt stamp on that first chunk.
Clear tailWaitState in the prune reset so the resumed chunk recomputes from the
emptied tail.

Adds a runtime guard (prune clears the cache) and a sim equivalence test
covering prune-then-resume stamping.

Co-authored-by: Orca <help@stably.ai>

* docs(runtime): reword wait-scan comment that named a removed function

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-05 23:21:35 -07:00

44 lines
1.7 KiB
TypeScript

/**
* Regression: pruneDisconnectedPtyTranscript empties a disconnected PTY's
* retained tail. The onPtyData wait-scan memoization caches the tail's wait
* state on the record (tailWaitState) and reuses it as the next chunk's
* "previous" state — so the prune MUST also clear that cache, or a record that
* resumes output after adoption/reattach would reuse a stale (pre-prune,
* possibly blocked) wait state and mis-stamp waitBlockedAt on its first chunk.
*/
import { describe, expect, it } from 'vitest'
import { OrcaRuntimeService } from './orca-runtime'
import type { TerminalTailWaitState } from './orca-runtime'
type PtyRecord = {
connected: boolean
tailBuffer: string[]
tailWaitState?: TerminalTailWaitState
}
type RuntimeInternals = {
recordPtyWorktree: (p: string, w: string, s?: { connected?: boolean }) => PtyRecord
pruneDisconnectedPtyTranscript: (pty: PtyRecord) => void
}
describe('pruneDisconnectedPtyTranscript clears the wait-scan cache', () => {
it('empties the tail and drops tailWaitState so resume recomputes', () => {
const runtime = new OrcaRuntimeService()
const internals = runtime as unknown as RuntimeInternals
const pty = internals.recordPtyWorktree('pty-1', 'wt-1', { connected: true })
// Simulate an established, blocked tail with a memoized wait state.
pty.tailBuffer = ['Update available! Press Enter to continue.']
pty.tailWaitState = {
waitText: 'update available! press enter to continue.',
signal: { reason: 'codex-update-prompt', index: 0 },
fromTail: true
}
pty.connected = false
internals.pruneDisconnectedPtyTranscript(pty)
expect(pty.tailBuffer).toEqual([])
expect(pty.tailWaitState).toBeUndefined()
})
})