mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-03 02:11:48 +00:00
fix(tui): bound retained state against idle OOM
Guards four unbounded growth paths reachable at idle — the shape matches reports of the TUI hitting V8's 2GB heap limit after ~1m of idle with 0 tokens used (Mark-Compact freed ~6MB of 2045MB → pure retention). - `GatewayClient.logs` + `gateway.stderr` events: 200-line cap is bytes- uncapped; a chatty Python child emitting multi-MB lines (traceback, dumped config, unsplit JSON) retains everything. Truncate at 4KB/line. - `GatewayClient.bufferedEvents`: unbounded until `drain()` fires. Cap at 2000 so a pre-mount event storm can't pin memory indefinitely. - `useMainApp` gateway `exit` handler: didn't reset `turnController`, so a mid-stream crash left `bufRef`/`reasoningText` alive forever. - `pasteSnips` count-capped (32) but byte-uncapped. Add a 4MB total cap and clear snips in `clearIn` so submitted pastes don't linger. - `StylePool.transitionCache`: uncapped `Map<number,string>`. Full-clear at 32k entries (mirrors `charCache` pattern).
This commit is contained in:
parent
424e9f36b0
commit
0d353ca6a8
4 changed files with 44 additions and 5 deletions
|
|
@ -16,6 +16,28 @@ import { pasteTokenLabel, stripTrailingPasteNewlines } from '../lib/text.js'
|
|||
import type { PasteSnippet, UseComposerStateOptions, UseComposerStateResult } from './interfaces.js'
|
||||
import { $isBlocked } from './overlayStore.js'
|
||||
|
||||
const PASTE_SNIP_MAX_COUNT = 32
|
||||
const PASTE_SNIP_MAX_TOTAL_BYTES = 4 * 1024 * 1024
|
||||
|
||||
const trimSnips = (snips: PasteSnippet[]): PasteSnippet[] => {
|
||||
let total = 0
|
||||
const out: PasteSnippet[] = []
|
||||
|
||||
for (let i = snips.length - 1; i >= 0; i--) {
|
||||
const snip = snips[i]!
|
||||
const size = snip.text.length
|
||||
|
||||
if (out.length >= PASTE_SNIP_MAX_COUNT || total + size > PASTE_SNIP_MAX_TOTAL_BYTES) {
|
||||
break
|
||||
}
|
||||
|
||||
total += size
|
||||
out.unshift(snip)
|
||||
}
|
||||
|
||||
return out.length === snips.length ? snips : out
|
||||
}
|
||||
|
||||
export function useComposerState({ gw, onClipboardPaste, submitRef }: UseComposerStateOptions): UseComposerStateResult {
|
||||
const [input, setInput] = useState('')
|
||||
const [inputBuf, setInputBuf] = useState<string[]>([])
|
||||
|
|
@ -31,6 +53,7 @@ export function useComposerState({ gw, onClipboardPaste, submitRef }: UseCompose
|
|||
const clearIn = useCallback(() => {
|
||||
setInput('')
|
||||
setInputBuf([])
|
||||
setPasteSnips([])
|
||||
setQueueEdit(null)
|
||||
setHistoryIdx(null)
|
||||
historyDraftRef.current = ''
|
||||
|
|
@ -68,7 +91,7 @@ export function useComposerState({ gw, onClipboardPaste, submitRef }: UseCompose
|
|||
const tail = cursor < value.length && !/\s/.test(value[cursor] ?? '') ? ' ' : ''
|
||||
const insert = `${lead}${label}${tail}`
|
||||
|
||||
setPasteSnips(prev => [...prev, { label, text: cleanedText }].slice(-32))
|
||||
setPasteSnips(prev => trimSnips([...prev, { label, text: cleanedText }]))
|
||||
|
||||
void gw
|
||||
.request<{ path?: string }>('paste.collapse', { text: cleanedText })
|
||||
|
|
|
|||
|
|
@ -448,6 +448,7 @@ export function useMainApp(gw: GatewayClient) {
|
|||
const handler = (ev: GatewayEvent) => onEventRef.current(ev)
|
||||
|
||||
const exitHandler = () => {
|
||||
turnController.reset()
|
||||
patchUiState({ busy: false, sid: null, status: 'gateway exited' })
|
||||
turnController.pushActivity('gateway exited · /logs to inspect', 'error')
|
||||
sys('error: gateway exited')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue