mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-19 10:02:16 +00:00
fix(desktop): avoid stack overflow rendering huge fenced blocks
`normalizeFenceBlocks`/`pushProseFence` appended block bodies with `out.push(...lines)`, which spreads every line as a separate call argument. A single message carrying a large fenced block (a logged minified bundle, base64 blob, or big tool dump — common in long sessions) overflows V8's argument-count limit and throws `RangeError: Maximum call stack size exceeded`, breaking the transcript render. Compression doesn't save us: it gates on tokens vs. window, not a single message's line count, and the protected recent tail renders verbatim regardless. Append iteratively via a small `extend()` helper. Behavior is identical for normal-sized blocks.
This commit is contained in:
parent
5e01a5dbf1
commit
547a014e7e
2 changed files with 25 additions and 6 deletions
|
|
@ -201,4 +201,13 @@ describe('preprocessMarkdown', () => {
|
|||
|
||||
expect(output).toContain('<https://example.com/a_b/c~d/page>')
|
||||
})
|
||||
|
||||
it('handles a fenced block larger than V8 spread-argument limit', () => {
|
||||
// A single huge code block (e.g. a logged minified bundle) used to throw
|
||||
// `RangeError: Maximum call stack size exceeded` via `out.push(...lines)`.
|
||||
const body = Array.from({ length: 200_000 }, (_, i) => `line ${i}`).join('\n')
|
||||
const input = `\`\`\`js\n${body}\n\`\`\``
|
||||
|
||||
expect(() => preprocessMarkdown(input)).not.toThrow()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -151,12 +151,22 @@ function normalizeVisibleProse(text: string): string {
|
|||
.join('')
|
||||
}
|
||||
|
||||
// `out.push(...lines)` spreads every element as a separate call argument, so a
|
||||
// single fenced block with tens of thousands of lines (a logged minified
|
||||
// bundle, base64 blob, huge tool dump) overflows V8's argument-count limit and
|
||||
// throws `RangeError: Maximum call stack size exceeded`. Append iteratively.
|
||||
function extend(out: string[], lines: string[]) {
|
||||
for (const line of lines) {
|
||||
out.push(line)
|
||||
}
|
||||
}
|
||||
|
||||
function pushProseFence(out: string[], indent: string, info: string, lines: string[]) {
|
||||
if (info) {
|
||||
out.push(`${indent}${info}`.trimEnd())
|
||||
}
|
||||
|
||||
out.push(...lines)
|
||||
extend(out, lines)
|
||||
}
|
||||
|
||||
function findClosingFence(lines: string[], start: number, marker: string): number {
|
||||
|
|
@ -241,7 +251,7 @@ function normalizeFenceBlocks(text: string): string {
|
|||
}
|
||||
|
||||
if (closeIndex !== -1 && isUrlOnlyBlock(bodyLines)) {
|
||||
out.push(...bodyLines)
|
||||
extend(out, bodyLines)
|
||||
index = closeIndex + 1
|
||||
|
||||
continue
|
||||
|
|
@ -264,10 +274,10 @@ function normalizeFenceBlocks(text: string): string {
|
|||
// any literal `$$` characters in the body don't collide with
|
||||
// an outer math wrapper. No close emitted yet — streaming.
|
||||
out.push(`${indent}${marker}math`)
|
||||
out.push(...bodyLines)
|
||||
extend(out, bodyLines)
|
||||
} else {
|
||||
out.push(`${indent}${marker}${language}`)
|
||||
out.push(...bodyLines)
|
||||
extend(out, bodyLines)
|
||||
}
|
||||
|
||||
break
|
||||
|
|
@ -288,7 +298,7 @@ function normalizeFenceBlocks(text: string): string {
|
|||
// colliding with our wrapper. Without this rewrite the block
|
||||
// would render as a syntax-highlighted "latex" code listing.
|
||||
out.push(`${indent}${marker}math`)
|
||||
out.push(...bodyLines)
|
||||
extend(out, bodyLines)
|
||||
out.push(`${indent}${marker}`)
|
||||
index = closeIndex + 1
|
||||
|
||||
|
|
@ -296,7 +306,7 @@ function normalizeFenceBlocks(text: string): string {
|
|||
}
|
||||
|
||||
out.push(`${indent}${marker}${language}`)
|
||||
out.push(...bodyLines)
|
||||
extend(out, bodyLines)
|
||||
out.push(`${indent}${marker}`)
|
||||
index = closeIndex + 1
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue