mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-19 10:02:16 +00:00
fix(desktop): isolate message render crashes from the root boundary
Streamdown runs our `preprocess` inside its own useMemo, and the user bubble runs `extractEmbeddedImages`/directive parsing inside theirs — so anything thrown while rendering one message (a regex/stack overflow on adversarial content) escapes to the ROOT error boundary and takes down the entire app, as seen in a reported `RangeError: Maximum call stack size exceeded` from a single message. Wrap both the assistant preprocess pipeline and the user-message directive passes in try/catch that degrade to the raw text. One bad message now renders plain instead of nuking the transcript.
This commit is contained in:
parent
547a014e7e
commit
b82eca2beb
2 changed files with 27 additions and 3 deletions
|
|
@ -327,8 +327,23 @@ function shortLabel(type: HermesRefType, id: string): string {
|
|||
* inline chips. Embedded MEDIA images render below as a thumbnail row.
|
||||
*/
|
||||
export function DirectiveContent({ text }: { text: string }) {
|
||||
const { cleanedText, images } = useMemo(() => extractEmbeddedImages(text ?? ''), [text])
|
||||
const segments = useMemo(() => hermesDirectiveFormatter.parse(cleanedText), [cleanedText])
|
||||
// Both passes run text through regexes; on pathological input they can throw
|
||||
// (or overflow) and, since this renders inside a useMemo under the message,
|
||||
// bubble up to the root error boundary. Degrade gracefully to plain text.
|
||||
const { cleanedText, images } = useMemo(() => {
|
||||
try {
|
||||
return extractEmbeddedImages(text ?? '')
|
||||
} catch {
|
||||
return { cleanedText: text ?? '', images: [] }
|
||||
}
|
||||
}, [text])
|
||||
const segments = useMemo(() => {
|
||||
try {
|
||||
return hermesDirectiveFormatter.parse(cleanedText)
|
||||
} catch {
|
||||
return [{ kind: 'text', text: cleanedText }] as Unstable_DirectiveSegment[]
|
||||
}
|
||||
}, [cleanedText])
|
||||
|
||||
return (
|
||||
<span className="whitespace-pre-line" data-slot="aui_directive-text">
|
||||
|
|
|
|||
|
|
@ -57,7 +57,16 @@ const mathPlugin = createMemoizedMathPlugin({ singleDollarTextMath: true })
|
|||
// flush) with a tail-bounded repair — see lib/remend-tail.ts. Must stay
|
||||
// module-scope so the prop identity is stable across renders.
|
||||
function preprocessWithTailRepair(text: string): string {
|
||||
return tailBoundedRemend(preprocessMarkdown(text))
|
||||
// Streamdown runs `preprocess` inside its own useMemo, so anything thrown
|
||||
// here escapes to the ROOT error boundary and takes down the whole app — a
|
||||
// single adversarial message (e.g. content that overflows a regex/stack)
|
||||
// shouldn't be able to do that. Degrade to the raw text instead; it still
|
||||
// renders, just without our cosmetic normalization.
|
||||
try {
|
||||
return tailBoundedRemend(preprocessMarkdown(text))
|
||||
} catch {
|
||||
return text
|
||||
}
|
||||
}
|
||||
|
||||
// Memoized block splitter. Streamdown calls `parseMarkdownIntoBlocks` (a full
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue