perf(agent-status): keep the shared transcript reader's carry linear (#10777)

readLastTextFromTranscriptOnce re-joined its carry buffer on every block that
held no newline, so a transcript whose tail is one oversized line copied
O(line^2). It backs three readers — the Claude/Codex user prompt, the Command
Code assistant message, and the shared assistant-text reader — so every agent
that resolves turn text from a transcript paid it.

Same chunk-list carry the Command Code prompt reader already uses. Measured on
a transcript whose tail is one big line: 15.24 ms -> 8.87 ms at 3.9 MB, and the
gap widens with the line, which is the quadratic signature.
This commit is contained in:
Neil 2026-07-26 20:31:46 -07:00 committed by GitHub
parent 8db61248ec
commit a8a2e6cb1f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 75 additions and 18 deletions

View File

@ -971,6 +971,56 @@ describe('shared agent-hook-listener', () => {
}
})
it('reads the last assistant message behind an oversized line without quadratic copying', () => {
const tmpDir = mkdtempSync(join(tmpdir(), 'orca-assistant-huge-line-'))
const transcriptPath = join(tmpDir, 'transcript.jsonl')
const originalConcat = Buffer.concat
let concatenatedBytes = 0
try {
// The shared backward reader (readLastTextFromTranscriptOnce) stitches a
// line spanning many read blocks. Re-joining the carry per block copies
// O(line^2); the chunk list defers to one join.
const lineBytes = 2 * 1024 * 1024
writeFileSync(
transcriptPath,
`${JSON.stringify({
role: 'assistant',
content: [{ type: 'text', text: 'answer behind a huge line' }]
})}\n${JSON.stringify({
role: 'user',
content: [{ type: 'text', text: 'x'.repeat(lineBytes) }]
})}\n`
)
Buffer.concat = ((list: readonly Uint8Array[], totalLength?: number) => {
const joined = originalConcat(list as Uint8Array[], totalLength)
concatenatedBytes += joined.length
return joined
}) as typeof Buffer.concat
const done = normalizeHookPayload(
state,
'claude',
{
paneKey: PANE_KEY,
tabId: 'tab-1',
worktreeId: 'wt',
env: 'production',
version: '1',
payload: { hook_event_name: 'Stop', transcript_path: transcriptPath }
},
'production'
)
expect(done?.payload.lastAssistantMessage).toBe('answer behind a huge line')
// Linear copies once (~lineBytes); the quadratic form copied many times that.
expect(concatenatedBytes).toBeLessThan(lineBytes * 4)
} finally {
Buffer.concat = originalConcat
rmSync(tmpDir, { recursive: true, force: true })
}
})
// Why these three: the prompt read scans backward from EOF and stops at the
// first user line, so the cases that can break are a prompt spanning a chunk
// boundary, a later prompt that must win over an earlier one, and the byte

View File

@ -1337,11 +1337,14 @@ function readLastTextFromTranscriptOnce(
}
const fd = openSync(transcriptPath, 'r')
try {
let carryBytes: Buffer = Buffer.alloc(0)
// Why a chunk list: carry holds a partial line, and re-joining it per block
// made one oversized line (a big tool result or pasted prompt) cost O(line^2).
let carryChunks: Buffer[] = []
let bytesRead = 0
while (bytesRead < size && bytesRead < TRANSCRIPT_MAX_SCAN_BYTES) {
const chunkSize = Math.min(size - bytesRead, TRANSCRIPT_CHUNK_BYTES)
const position = size - bytesRead - chunkSize
let scanEnd = size
while (scanEnd > 0 && bytesRead < TRANSCRIPT_MAX_SCAN_BYTES) {
const chunkSize = Math.min(scanEnd, TRANSCRIPT_CHUNK_BYTES)
const position = scanEnd - chunkSize
const buffer = Buffer.alloc(chunkSize)
let filled = 0
while (filled < chunkSize) {
@ -1351,25 +1354,30 @@ function readLastTextFromTranscriptOnce(
}
filled += n
}
const n = filled
bytesRead += n
if (n === 0) {
// Why bail on a short read: the file shrank under us, so the bytes above
// this block no longer line up with what the earlier ones assumed.
if (filled < chunkSize) {
break
}
const combined = Buffer.concat([buffer.subarray(0, n), carryBytes])
const atStart = bytesRead >= size
const firstNewline = combined.indexOf(0x0a)
bytesRead += filled
scanEnd = position
// Why search only the new block: carry is always the run before a newline,
// so it holds none of its own.
const firstNewline = buffer.indexOf(0x0a)
const atStart = position === 0
let completeRegion: Buffer
let nextCarry: Buffer
if (atStart) {
completeRegion = combined
nextCarry = Buffer.alloc(0)
completeRegion =
carryChunks.length === 0 ? buffer : Buffer.concat([buffer, ...carryChunks])
carryChunks = []
} else if (firstNewline === -1) {
completeRegion = Buffer.alloc(0)
nextCarry = combined
completeRegion = EMPTY_TRANSCRIPT_REGION
carryChunks.unshift(buffer)
} else {
nextCarry = combined.subarray(0, firstNewline)
completeRegion = combined.subarray(firstNewline + 1)
const afterNewline = buffer.subarray(firstNewline + 1)
completeRegion =
carryChunks.length === 0 ? afterNewline : Buffer.concat([afterNewline, ...carryChunks])
carryChunks = [buffer.subarray(0, firstNewline)]
}
if (completeRegion.length > 0) {
const extracted = findLastExtractedTranscriptLineText(
@ -1380,7 +1388,6 @@ function readLastTextFromTranscriptOnce(
return extracted
}
}
carryBytes = nextCarry
}
return undefined
} finally {