mirror of
https://github.com/stablyai/orca.git
synced 2026-10-21 16:03:26 +00:00
perf(terminal): bound the reattach payload agent-signal scan (#12681)
hasCursorAgentReattachPayloadScreenSignal built a char-by-char copy of the entire reattach payload so it could read the last header plus 5000 chars. On a 2MB daemon snapshot that cost 17.5ms of synchronous renderer main-thread work — ~75% of what xterm then spends parsing the same bytes — and the miss case paid it in full for a result that is always false. Two changes, both matching existing in-tree precedent: bound the scan to a 256KB tail (as the kitty tracker already bounds its own scan), and strip via the shared precompiled CSI_SEQUENCE_PATTERN instead of a hand-rolled loop, which is also faster in V8 because it copies spans rather than building a rope per character. 2MB snapshot, header hit 17.5ms -> 0.80ms (22x) 2MB snapshot, miss 8.7ms -> 0.52ms (17x) 200KB snapshot, header hit 1.5ms -> 0.62ms (2.4x) config/scripts/terminal-reattach-payload-scan-benchmark.mjs reproduces this and asserts every candidate agrees with the baseline before timing it. It also records a negative result: porting the daemon mouse mirror's includes() pre-filter to the kitty tracker makes reattach slower, because snapshots always contain the introducer. Adds guards for the two behaviours a future shortcut would silently break: a CSI-split header must still match, and a header behind the tail bound must not. Also byte-pins POST_REPLAY_REATTACH_RESET_KEEP_MOUSE, which shipped unpinned. Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -16427,6 +16427,81 @@ describe('connectPanePty', () => {
|
||||
disposable.dispose()
|
||||
})
|
||||
|
||||
// Why pinned: the classifier strips CSI precisely so a styled header still matches. A future
|
||||
// "just lastIndexOf the raw bytes" shortcut would pass every other test and silently break this.
|
||||
it('still detects the Cursor Agent screen when CSI styling splits the header and the marker', async () => {
|
||||
const { connectPanePty } = await import('./pty-connection')
|
||||
enableActiveRuntimeEnvironment()
|
||||
const transport = createMockTransport('remote:env-1@@terminal-1')
|
||||
const capturedReplayCallback: {
|
||||
current: ((data: string, meta?: { clearBeforeReplay?: boolean }) => void) | null
|
||||
} = { current: null }
|
||||
transport.connect.mockImplementation(async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
|
||||
capturedReplayCallback.current = callbacks.onReplayData ?? null
|
||||
return { id: 'remote:env-1@@terminal-1', replay: '' }
|
||||
})
|
||||
transportFactoryQueue.push(transport)
|
||||
setReattachPaneTitle('renamed shell')
|
||||
|
||||
const pane = createPane(1)
|
||||
const textarea = {} as HTMLTextAreaElement
|
||||
configureTerminalFocusMode(pane, textarea)
|
||||
const manager = createManager(1)
|
||||
const deps = createDeps()
|
||||
const disposable = await withMockedDocumentActiveElement(textarea, async () => {
|
||||
const connection = connectPanePty(pane as never, manager as never, deps as never)
|
||||
await flushAsyncTicks(6)
|
||||
|
||||
capturedReplayCallback.current?.(
|
||||
'\x1b[4;3HCursor \x1b[1mAgent\x1b[0m\x1b[9;3H→\x1b[0m Plan, search, build anything'
|
||||
)
|
||||
await flushAsyncTicks(12)
|
||||
|
||||
expect(pane.terminal.write).toHaveBeenCalledWith(
|
||||
POST_REPLAY_LIVE_AGENT_REATTACH_RESET,
|
||||
expect.any(Function)
|
||||
)
|
||||
return connection
|
||||
})
|
||||
disposable.dispose()
|
||||
})
|
||||
|
||||
// Why pinned: the scan reads only a bounded tail, so a header buried behind megabytes of
|
||||
// scrollback describes a finished run, not the current screen.
|
||||
it('ignores a Cursor Agent screen buried beyond the payload scan tail limit', async () => {
|
||||
const { connectPanePty } = await import('./pty-connection')
|
||||
enableActiveRuntimeEnvironment()
|
||||
const transport = createMockTransport('remote:env-1@@terminal-1')
|
||||
const capturedReplayCallback: {
|
||||
current: ((data: string, meta?: { clearBeforeReplay?: boolean }) => void) | null
|
||||
} = { current: null }
|
||||
transport.connect.mockImplementation(async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
|
||||
capturedReplayCallback.current = callbacks.onReplayData ?? null
|
||||
return { id: 'remote:env-1@@terminal-1', replay: '' }
|
||||
})
|
||||
transportFactoryQueue.push(transport)
|
||||
setReattachPaneTitle('renamed shell')
|
||||
|
||||
const pane = createPane(1)
|
||||
const textarea = {} as HTMLTextAreaElement
|
||||
configureTerminalFocusMode(pane, textarea)
|
||||
const manager = createManager(1)
|
||||
const deps = createDeps()
|
||||
const disposable = await withMockedDocumentActiveElement(textarea, async () => {
|
||||
const connection = connectPanePty(pane as never, manager as never, deps as never)
|
||||
await flushAsyncTicks(6)
|
||||
|
||||
capturedReplayCallback.current?.(
|
||||
`${ANSI_POSITIONED_CURSOR_AGENT_REATTACH_SCREEN}${'shell scrollback\r\n'.repeat(20_000)}`
|
||||
)
|
||||
await flushAsyncTicks(12)
|
||||
|
||||
expect(transport.sendInput).not.toHaveBeenCalledWith('\x1b[I')
|
||||
return connection
|
||||
})
|
||||
disposable.dispose()
|
||||
})
|
||||
|
||||
it('downgrades a scrollback-only Cursor Agent signal when the parsed viewport shows a shell', async () => {
|
||||
const { connectPanePty } = await import('./pty-connection')
|
||||
enableActiveRuntimeEnvironment()
|
||||
|
||||
@@ -115,6 +115,7 @@ import {
|
||||
queuePanePtyResizeIfHeld,
|
||||
type PanePtyResizeHoldFlushDetail
|
||||
} from '@/lib/pane-manager/pane-pty-resize-hold'
|
||||
import { CSI_SEQUENCE_PATTERN } from '../../../../shared/ansi-escape-sequences'
|
||||
import {
|
||||
buildPostReplayLiveAgentReattachReset,
|
||||
POST_REPLAY_LIVE_AGENT_SNAPSHOT_RESET,
|
||||
@@ -469,33 +470,22 @@ function terminalOwnsDomFocus(terminal: TerminalWithFocusMode): boolean {
|
||||
return document.activeElement === terminal.textarea
|
||||
}
|
||||
|
||||
function stripAnsiCsiSequences(data: string): string {
|
||||
let normalized = ''
|
||||
let index = 0
|
||||
while (index < data.length) {
|
||||
if (data.charCodeAt(index) === 0x1b && data[index + 1] === '[') {
|
||||
index += 2
|
||||
while (index < data.length) {
|
||||
const code = data.charCodeAt(index)
|
||||
index += 1
|
||||
if (code >= 0x40 && code <= 0x7e) {
|
||||
break
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
normalized += data[index]
|
||||
index += 1
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
const CURSOR_AGENT_REATTACH_HEADER = 'Cursor Agent'
|
||||
const CURSOR_AGENT_REATTACH_INPUT_MARKER = '→'
|
||||
const CURSOR_AGENT_REATTACH_SCREEN_SIGNAL_MAX_CHARS = 5000
|
||||
// Why bounded: reattach payloads reach multiple MB, but every replay puts the current screen last
|
||||
// and only the header nearest the end matters. 256KB clears even a fully SGR-styled frame by ~2x,
|
||||
// so the cut only ever drops stale scrollback — which would have been rejected anyway.
|
||||
const CURSOR_AGENT_REATTACH_SCAN_TAIL_LIMIT_CHARS = 256 * 1024
|
||||
|
||||
function hasCursorAgentReattachPayloadScreenSignal(data: string): boolean {
|
||||
const normalized = stripAnsiCsiSequences(data)
|
||||
const tail =
|
||||
data.length > CURSOR_AGENT_REATTACH_SCAN_TAIL_LIMIT_CHARS
|
||||
? data.slice(-CURSOR_AGENT_REATTACH_SCAN_TAIL_LIMIT_CHARS)
|
||||
: data
|
||||
// Why CSI only: OSC-carried titles must keep counting as a header occurrence, as they did when
|
||||
// this stripped CSI by hand.
|
||||
const normalized = tail.replace(CSI_SEQUENCE_PATTERN, '')
|
||||
// Why: anchor on the LAST header occurrence — replay buffers keep scrollback,
|
||||
// and an earlier finished run must not classify the current screen.
|
||||
const headerIndex = normalized.lastIndexOf(CURSOR_AGENT_REATTACH_HEADER)
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
POST_REPLAY_LIVE_SNAPSHOT_RESET,
|
||||
POST_REPLAY_MODE_RESET,
|
||||
POST_REPLAY_REATTACH_RESET,
|
||||
POST_REPLAY_REATTACH_RESET_KEEP_MOUSE,
|
||||
RESET_MOUSE_REPORTING,
|
||||
buildPostReplayLiveAgentReattachReset,
|
||||
replayPayloadEndsWithCursorHidden
|
||||
@@ -33,6 +34,17 @@ describe('terminal mode reset profiles', () => {
|
||||
expect(POST_REPLAY_REATTACH_RESET).not.toContain('\x1b[?2004l')
|
||||
})
|
||||
|
||||
// Why ?1004l stays: #944 — a hard-killed TUI leaves the daemon emulator on the alternate buffer,
|
||||
// so this profile can reach a plain shell, where armed focus reporting rings BEL on every pane
|
||||
// switch. Dropping it would also make this byte-identical to the live-agent profile.
|
||||
it('pins the live alternate-screen profile, which keeps mouse reporting but not focus', () => {
|
||||
expect(POST_REPLAY_REATTACH_RESET_KEEP_MOUSE).toBe(
|
||||
'\x1b[0 q\x1b[<99u\x1b[=0u\x1b[?25h\x1b[?1004l'
|
||||
)
|
||||
expect(POST_REPLAY_REATTACH_RESET_KEEP_MOUSE).not.toContain(RESET_MOUSE_REPORTING)
|
||||
expect(POST_REPLAY_REATTACH_RESET_KEEP_MOUSE).not.toBe(POST_REPLAY_LIVE_AGENT_REATTACH_RESET)
|
||||
})
|
||||
|
||||
// Why: #12101 — a cold-restored seed re-arms mouse reporting for a dead TUI.
|
||||
it('disarms mouse reporting on the cold-restore seed', () => {
|
||||
expect(COLD_RESTORE_SEED_MODE_RESET).toBe(RESET_MOUSE_REPORTING)
|
||||
|
||||
Reference in New Issue
Block a user