mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 08:02:31 +00:00
* fix(memory): detach retained CI and terminal tails from oversized strings * fix(terminal): detach retained error and reattach string slices * fix(terminal): release oversized recent-output backing strings * fix(terminal): release backing strings held by PTY detectors * fix(memory): own bounded Claude background task labels * fix: detach retained terminal mode scan tails * fix: own retained plugin worker output strings * fix: own incomplete OSC 133 carry strings --------- Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
89 lines
3.7 KiB
TypeScript
89 lines
3.7 KiB
TypeScript
import {
|
|
clampUtf8TextTail,
|
|
getUtf8ByteLength,
|
|
isUtf8ByteLengthWithinLimit
|
|
} from './utf8-byte-limits'
|
|
import { ownRetainedString } from './own-retained-string'
|
|
|
|
export const PR_CHECK_LOG_TAIL_LINES = 200
|
|
export const PR_CHECK_LOG_TAIL_RECENT_LINES = 100
|
|
export const PR_CHECK_LOG_TAIL_BYTES = 16 * 1024
|
|
export const PR_CHECK_LOG_TAIL_ERROR_CONTEXT_LINES = 2
|
|
export const PR_CHECK_LOG_TAIL_MAX_EARLIER_LINES = 30
|
|
export const PR_CHECK_LOG_TAIL_EARLIER_SEPARATOR = '… earlier errors …'
|
|
|
|
// Why: install/cache steps can flood the tail; pull a small window around earlier
|
|
// error markers so failures remain visible without downloading the full job log.
|
|
const ERROR_LINE_PATTERN =
|
|
/(?:##\[error\]|::error::|::error\b|\berror:|FAILED|exit code|ENOENT|EACCES|panic:|AssertionError)/i
|
|
|
|
function applyLogTailByteCap(text: string): string {
|
|
if (isUtf8ByteLengthWithinLimit(text, PR_CHECK_LOG_TAIL_BYTES)) {
|
|
return text
|
|
}
|
|
return clampUtf8TextTail(text, PR_CHECK_LOG_TAIL_BYTES).text
|
|
}
|
|
|
|
function joinLogExcerptWithByteCap(prefixLines: string[], recentLines: string[]): string {
|
|
const prefix = prefixLines.join('\n')
|
|
// UTF-16 length is a lower bound; oversized prefixes need no exact byte count.
|
|
const prefixByteLength =
|
|
prefix.length >= PR_CHECK_LOG_TAIL_BYTES ? PR_CHECK_LOG_TAIL_BYTES : getUtf8ByteLength(prefix)
|
|
if (prefixByteLength >= PR_CHECK_LOG_TAIL_BYTES) {
|
|
return clampUtf8TextTail(prefix, PR_CHECK_LOG_TAIL_BYTES).text
|
|
}
|
|
|
|
const separator = prefix.length > 0 && recentLines.length > 0 ? '\n' : ''
|
|
const recentBudget = PR_CHECK_LOG_TAIL_BYTES - prefixByteLength - getUtf8ByteLength(separator)
|
|
const recentTail = clampUtf8TextTail(recentLines.join('\n'), recentBudget).text
|
|
return `${prefix}${separator}${recentTail}`
|
|
}
|
|
|
|
function collectEarlierErrorLineIndexes(lines: string[], recentStart: number): number[] {
|
|
const indexes = new Set<number>()
|
|
// Newest-first errors and descending windows add the newest unique indexes first.
|
|
for (let index = recentStart - 1; index >= 0; index -= 1) {
|
|
if (!ERROR_LINE_PATTERN.test(lines[index] ?? '')) {
|
|
continue
|
|
}
|
|
const contextStart = Math.max(0, index - PR_CHECK_LOG_TAIL_ERROR_CONTEXT_LINES)
|
|
const contextEnd = Math.min(recentStart - 1, index + PR_CHECK_LOG_TAIL_ERROR_CONTEXT_LINES)
|
|
for (let contextIndex = contextEnd; contextIndex >= contextStart; contextIndex -= 1) {
|
|
indexes.add(contextIndex)
|
|
if (indexes.size === PR_CHECK_LOG_TAIL_MAX_EARLIER_LINES) {
|
|
return [...indexes].sort((left, right) => left - right)
|
|
}
|
|
}
|
|
}
|
|
return [...indexes].sort((left, right) => left - right)
|
|
}
|
|
|
|
function buildCheckLogTail(logText: string): string {
|
|
const lines = logText.split(/\r?\n/)
|
|
const recentStart = Math.max(0, lines.length - PR_CHECK_LOG_TAIL_RECENT_LINES)
|
|
const recentLines = lines.slice(recentStart)
|
|
|
|
if (recentStart === 0) {
|
|
return applyLogTailByteCap(lines.join('\n'))
|
|
}
|
|
|
|
const earlierIndexes = collectEarlierErrorLineIndexes(lines, recentStart)
|
|
if (earlierIndexes.length === 0) {
|
|
return applyLogTailByteCap(lines.slice(-PR_CHECK_LOG_TAIL_LINES).join('\n'))
|
|
}
|
|
|
|
const cappedEarlierIndexes = earlierIndexes.slice(-PR_CHECK_LOG_TAIL_MAX_EARLIER_LINES)
|
|
const earlierLines = cappedEarlierIndexes.map((index) => lines[index] ?? '')
|
|
// Why: if the recent tail alone exceeds the byte cap, keep the earlier error
|
|
// context visible instead of truncating it back out of the combined excerpt.
|
|
return joinLogExcerptWithByteCap(
|
|
[...earlierLines, PR_CHECK_LOG_TAIL_EARLIER_SEPARATOR],
|
|
recentLines
|
|
)
|
|
}
|
|
|
|
export function sliceCheckLogTail(logText: string): string {
|
|
// Cached excerpts must not pin the downloaded log behind a small V8 slice.
|
|
return ownRetainedString(buildCheckLogTail(logText))
|
|
}
|