mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-28 06:51:16 +08:00
Trim narration comments and collapse small one-off helpers in the remaining ui-tui perf support files while preserving behaviour.
122 lines
5.1 KiB
JavaScript
122 lines
5.1 KiB
JavaScript
#!/usr/bin/env node
|
||
/* global Buffer, console, process, setImmediate */
|
||
import inspector from 'node:inspector'
|
||
import { performance } from 'node:perf_hooks'
|
||
|
||
import React from 'react'
|
||
import { render } from '@hermes/ink'
|
||
import { AppLayout } from '../src/components/appLayout.tsx'
|
||
import { resetOverlayState } from '../src/app/overlayStore.ts'
|
||
import { resetTurnState } from '../src/app/turnStore.ts'
|
||
import { resetUiState } from '../src/app/uiStore.ts'
|
||
|
||
const session = new inspector.Session()
|
||
session.connect()
|
||
const post = (method, params = {}) => new Promise((resolve, reject) => {
|
||
session.post(method, params, (err, result) => err ? reject(err) : resolve(result))
|
||
})
|
||
|
||
const historySize = Number(process.env.HISTORY || 500)
|
||
const mountedRows = Number(process.env.MOUNTED || 120)
|
||
|
||
class Sink {
|
||
columns = Number(process.env.COLS || 120)
|
||
rows = Number(process.env.ROWS || 42)
|
||
isTTY = true
|
||
bytes = 0
|
||
writes = 0
|
||
listeners = new Map()
|
||
write(chunk) {
|
||
this.bytes += Buffer.byteLength(String(chunk ?? ''))
|
||
this.writes++
|
||
return true
|
||
}
|
||
on(event, fn) { this.listeners.set(event, fn); return this }
|
||
off(event) { this.listeners.delete(event); return this }
|
||
once(event, fn) { this.listeners.set(event, fn); return this }
|
||
removeListener(event) { this.listeners.delete(event); return this }
|
||
}
|
||
|
||
const theme = {
|
||
brand: { prompt: '›' },
|
||
color: {
|
||
amber: '#d19a66', bronze: '#8b6f47', dim: '#6b7280', error: '#ff5555', gold: '#ffd166', label: '#61afef',
|
||
ok: '#98c379', warn: '#e5c07b', cornsilk: '#fff8dc', prompt: '#c678dd', shellDollar: '#98c379',
|
||
statusCritical: '#ff5555', statusBad: '#e06c75', statusWarn: '#e5c07b', statusGood: '#98c379',
|
||
selectionBg: '#44475a'
|
||
}
|
||
}
|
||
|
||
const noop = () => {}
|
||
const historyItems = [
|
||
{ kind: 'intro', role: 'system', text: '', info: { model: 'test', tools: {}, skills: {}, version: 'test' } },
|
||
...Array.from({ length: historySize }, (_, i) => ({
|
||
role: i % 5 === 0 ? 'user' : 'assistant',
|
||
text: `message ${i}\n${'lorem ipsum '.repeat(80)}`
|
||
}))
|
||
]
|
||
const scrollRef = { current: {
|
||
getScrollTop: () => 0,
|
||
getPendingDelta: () => 0,
|
||
getScrollHeight: () => historySize * 4,
|
||
getViewportHeight: () => 30,
|
||
getViewportTop: () => 0,
|
||
isSticky: () => true,
|
||
subscribe: () => () => {},
|
||
scrollBy: noop,
|
||
scrollTo: noop,
|
||
scrollToBottom: noop,
|
||
setClampBounds: noop,
|
||
getLastManualScrollAt: () => 0
|
||
} }
|
||
|
||
const baseProps = streamingText => ({
|
||
actions: { answerApproval: noop, answerClarify: noop, answerSecret: noop, answerSudo: noop, onModelSelect: noop, resumeById: noop, setStickyPrompt: noop },
|
||
composer: { cols: 120, compIdx: 0, completions: [], empty: false, handleTextPaste: () => null, input: '', inputBuf: [], pagerPageSize: 10, queueEditIdx: null, queuedDisplay: [], submit: noop, updateInput: noop },
|
||
mouseTracking: false,
|
||
progress: {
|
||
activity: [], outcome: '', reasoning: streamingText, reasoningActive: true, reasoningStreaming: true,
|
||
reasoningTokens: Math.ceil(streamingText.length / 4), showProgressArea: true, showStreamingArea: true,
|
||
streamPendingTools: [], streamSegments: [], streaming: streamingText, subagents: [], toolTokens: 0, tools: [], turnTrail: [], todos: []
|
||
},
|
||
status: { cwdLabel: '~/repo', goodVibesTick: 0, sessionStartedAt: Date.now(), showStickyPrompt: false, statusColor: theme.color.ok, stickyPrompt: '', turnStartedAt: Date.now(), voiceLabel: 'voice off' },
|
||
transcript: {
|
||
historyItems,
|
||
scrollRef,
|
||
virtualHistory: { bottomSpacer: 0, end: historyItems.length, measureRef: () => noop, offsets: historyItems.map((_, i) => i * 4), start: Math.max(0, historyItems.length - mountedRows), topSpacer: 0 },
|
||
virtualRows: historyItems.map((msg, index) => ({ index, key: `m${index}`, msg }))
|
||
}
|
||
})
|
||
|
||
async function main() {
|
||
resetUiState()
|
||
resetTurnState()
|
||
resetOverlayState()
|
||
const stdout = new Sink()
|
||
const stdin = { isTTY: true, setRawMode: noop, on: noop, off: noop, resume: noop, pause: noop }
|
||
const text = Array.from({ length: Number(process.env.LINES || 1200) }, (_, i) => `stream line ${i} ${'x'.repeat(90)}`).join('\n')
|
||
const inst = render(React.createElement(AppLayout, baseProps('')), { stdout, stdin, stderr: stdout, debug: false, exitOnCtrlC: false })
|
||
|
||
await post('Profiler.enable')
|
||
await post('HeapProfiler.enable')
|
||
await post('Profiler.start')
|
||
const startMem = process.memoryUsage()
|
||
const t0 = performance.now()
|
||
const iterations = Number(process.env.ITERS || 40)
|
||
for (let i = 1; i <= iterations; i++) {
|
||
const prefix = text.slice(0, Math.floor(text.length * i / iterations))
|
||
inst.rerender(React.createElement(AppLayout, baseProps(prefix)))
|
||
await new Promise(r => setImmediate(r))
|
||
}
|
||
const elapsed = performance.now() - t0
|
||
const prof = await post('Profiler.stop')
|
||
const endMem = process.memoryUsage()
|
||
await post('HeapProfiler.collectGarbage')
|
||
const afterGc = process.memoryUsage()
|
||
inst.unmount()
|
||
session.disconnect()
|
||
console.log(JSON.stringify({ elapsedMs: Math.round(elapsed), stdoutBytes: stdout.bytes, stdoutWrites: stdout.writes, startMem, endMem, afterGc, profileNodes: prof.profile.nodes.length }, null, 2))
|
||
}
|
||
|
||
main().catch(err => { console.error(err); process.exit(1) })
|