mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-02 02:01:47 +00:00
refactor(tui): /clean pass across ui-tui — 49 files, −217 LOC
Full codebase pass using the /clean doctrine (KISS/DRY, no one-off
helpers, no variables-used-once, pure functional where natural,
inlined obvious one-liners, killed dead exports, narrowed types,
spaced JSX). All contracts preserved — no RPC method, event name,
or exported type shape changed.
app/ — 15 files, -134 LOC
- inlined 4 one-off helpers (titleCase, isLong, statusToneFrom,
focusOutside predicate)
- stores to arrow-const style (buildUiState, buildTurnState,
buildOverlayState plus get/patch/reset triplets)
- functional slash/registry byName map (flatMap over for-loops)
- dropped dead param `live` in cancelOverlayFromCtrlC
- DRY'd duplicate shift() call in scrollWithSelection
- consolidated sections.push calls in /help
components/ — 12 files, -40 LOC
- extracted inline prop types to interfaces at file bottom (13×)
- inlined 6 one-off vars (pctLabel, logoW, heroW, cwd, title, hint)
- promoted HEART_COLORS + OPTS/LABELS to module scope
- JSX sibling spacing across 9 files
- un-shadowed `raw` in textInput
- components/thinking.tsx + components/markdown.tsx untouched
(structurally load-bearing / edge-case-heavy)
config content domain protocol/ — 8 files, -77 LOC
- tightened 3 regexes (MOUSE_TRACKING, looksLikeSlashCommand,
hasInterpolation — dropped stateful lastIndex dance)
- dead export ParsedSlashCommand removed
- MODES narrowed to `as const`, `.find(m => m === s)` replaces
`.includes() ? (as cast) : null`
- fortunes.ts hash via reduce
- fmtDuration ternary chain
- inlined aboveViewport predicate in viewport.ts
hooks/ + lib/ — 9 files, -38 LOC
- ANSI_RE via String.fromCharCode(27) + WS_RE lifted to module
scope (no more eslint-disable no-control-regex)
- compactPreview/edgePreview/thinkingPreview → ternary arrows
- useCompletion: hoisted pathReplace, moved stale-ref guard earlier
- useInputHistory: dropped useCallback wrapper (append is stable)
- useVirtualHistory: replaced 4× any with unknown + narrow
MeasuredNode interface + one cast site
root TS — 3 files, -63 LOC
- banner.ts: parseRichMarkup via matchAll instead of exec/lastIndex,
artWidth via reduce
- gatewayClient.ts: resolvePython candidate list collapse, inlined
one-branch guards in dispatch/pushLog/drain/request
- types.ts: alpha-sorted ActiveTool / Msg / SudoReq / SecretReq
members
eslint config
- disabled react-hooks/exhaustive-deps on packages/hermes-ink/**
(compiled by react/compiler, deps live in $[N] memo arrays that
eslint can't introspect) and removed the now-orphan in-file
disable directive in ScrollBox.tsx
fixes (not from the cleaner pass)
- useComposerState: unlinkSync(file) + try/catch → rmSync(file,
{ force: true }) — kills the no-empty lint error and is more
idiomatic
- useConfigSync: added setBellOnComplete + setVoiceEnabled to the
two useEffect dep arrays (they're stable React setState setters;
adding is safe and silences exhaustive-deps)
verification
- npx eslint src/ packages/ → 0 errors, 0 warnings
- npm run type-check → clean
- npm test → 50/50
- npm run build → 394.8kb ink-bundle.js, 11ms esbuild
- pytest tests/tui_gateway/ tests/test_tui_gateway_server.py
tests/hermes_cli/test_tui_resume_flow.py
tests/hermes_cli/test_tui_npm_install.py → 57/57
This commit is contained in:
parent
c730ab8ad7
commit
39231f29c6
49 changed files with 527 additions and 744 deletions
|
|
@ -1,12 +1,13 @@
|
|||
import { THINKING_COT_MAX } from '../config/limits.js'
|
||||
import type { ThinkingMode } from '../types.js'
|
||||
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const ANSI_RE = /\x1b\[[0-9;]*m/g
|
||||
const ESC = String.fromCharCode(27)
|
||||
const ANSI_RE = new RegExp(`${ESC}\\[[0-9;]*m`, 'g')
|
||||
const WS_RE = /\s+/g
|
||||
|
||||
export const stripAnsi = (s: string) => s.replace(ANSI_RE, '')
|
||||
|
||||
export const hasAnsi = (s: string) => s.includes('\x1b[') || s.includes('\x1b]')
|
||||
export const hasAnsi = (s: string) => s.includes(`${ESC}[`) || s.includes(`${ESC}]`)
|
||||
|
||||
const renderEstimateLine = (line: string) => {
|
||||
const trimmed = line.trim()
|
||||
|
|
@ -38,7 +39,7 @@ const renderEstimateLine = (line: string) => {
|
|||
}
|
||||
|
||||
export const compactPreview = (s: string, max: number) => {
|
||||
const one = s.replace(/\s+/g, ' ').trim()
|
||||
const one = s.replace(WS_RE, ' ').trim()
|
||||
|
||||
return !one ? '' : one.length > max ? one.slice(0, max - 1) + '…' : one
|
||||
}
|
||||
|
|
@ -46,17 +47,13 @@ export const compactPreview = (s: string, max: number) => {
|
|||
export const estimateTokensRough = (text: string) => (!text ? 0 : (text.length + 3) >> 2)
|
||||
|
||||
export const edgePreview = (s: string, head = 16, tail = 28) => {
|
||||
const one = s.replace(/\s+/g, ' ').trim().replace(/\]\]/g, '] ]')
|
||||
const one = s.replace(WS_RE, ' ').trim().replace(/\]\]/g, '] ]')
|
||||
|
||||
if (!one) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (one.length <= head + tail + 4) {
|
||||
return one
|
||||
}
|
||||
|
||||
return `${one.slice(0, head).trimEnd()}.. ${one.slice(-tail).trimStart()}`
|
||||
return !one
|
||||
? ''
|
||||
: one.length <= head + tail + 4
|
||||
? one
|
||||
: `${one.slice(0, head).trimEnd()}.. ${one.slice(-tail).trimStart()}`
|
||||
}
|
||||
|
||||
export const pasteTokenLabel = (text: string, lineCount: number) => {
|
||||
|
|
@ -76,15 +73,7 @@ export const pasteTokenLabel = (text: string, lineCount: number) => {
|
|||
export const thinkingPreview = (reasoning: string, mode: ThinkingMode, max: number = THINKING_COT_MAX) => {
|
||||
const raw = reasoning.trim()
|
||||
|
||||
if (!raw || mode === 'collapsed') {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (mode === 'full') {
|
||||
return raw
|
||||
}
|
||||
|
||||
return compactPreview(raw.replace(/\s+/g, ' '), max)
|
||||
return !raw || mode === 'collapsed' ? '' : mode === 'full' ? raw : compactPreview(raw.replace(WS_RE, ' '), max)
|
||||
}
|
||||
|
||||
export const stripTrailingPasteNewlines = (text: string) => (/[^\n]/.test(text) ? text.replace(/\n+$/, '') : text)
|
||||
|
|
@ -97,18 +86,18 @@ export const toolTrailLabel = (name: string) =>
|
|||
.join(' ') || name
|
||||
|
||||
export const formatToolCall = (name: string, context = '') => {
|
||||
const label = toolTrailLabel(name)
|
||||
const preview = compactPreview(context, 64)
|
||||
|
||||
return preview ? `${toolTrailLabel(name)}("${preview}")` : toolTrailLabel(name)
|
||||
return preview ? `${label}("${preview}")` : label
|
||||
}
|
||||
|
||||
export const buildToolTrailLine = (name: string, context: string, error?: boolean, note?: string): string => {
|
||||
export const buildToolTrailLine = (name: string, context: string, error?: boolean, note?: string) => {
|
||||
const detail = compactPreview(note ?? '', 72)
|
||||
|
||||
return `${formatToolCall(name, context)}${detail ? ` :: ${detail}` : ''} ${error ? ' ✗' : ' ✓'}`
|
||||
}
|
||||
|
||||
/** Tool completed / failed row in the inline trail (not CoT prose). */
|
||||
export const isToolTrailResultLine = (line: string) => line.endsWith(' ✓') || line.endsWith(' ✗')
|
||||
|
||||
export const parseToolTrailResultLine = (line: string) => {
|
||||
|
|
@ -133,10 +122,8 @@ export const parseToolTrailResultLine = (line: string) => {
|
|||
return { call: body, detail: '', mark }
|
||||
}
|
||||
|
||||
/** Ephemeral status lines that should vanish once the next phase starts. */
|
||||
export const isTransientTrailLine = (line: string) => line.startsWith('drafting ') || line === 'analyzing tool output…'
|
||||
|
||||
/** Whether a persisted/activity tool line belongs to the same tool label as a newer line. */
|
||||
export const sameToolTrailGroup = (label: string, entry: string) =>
|
||||
entry === `${label} ✓` ||
|
||||
entry === `${label} ✗` ||
|
||||
|
|
@ -144,7 +131,6 @@ export const sameToolTrailGroup = (label: string, entry: string) =>
|
|||
entry.startsWith(`${label} ::`) ||
|
||||
entry.startsWith(`${label}:`)
|
||||
|
||||
/** Index of the last non-result trail line, or -1. */
|
||||
export const lastCotTrailIndex = (trail: readonly string[]) => {
|
||||
for (let i = trail.length - 1; i >= 0; i--) {
|
||||
if (!isToolTrailResultLine(trail[i]!)) {
|
||||
|
|
@ -168,10 +154,7 @@ export const estimateRows = (text: string, w: number, compact = false) => {
|
|||
const lang = maybeFence[2]!.trim()
|
||||
|
||||
if (!fence) {
|
||||
fence = {
|
||||
char: marker[0] as '`' | '~',
|
||||
len: marker.length
|
||||
}
|
||||
fence = { char: marker[0] as '`' | '~', len: marker.length }
|
||||
|
||||
if (lang) {
|
||||
rows += Math.ceil((`─ ${lang}`.length || 1) / w)
|
||||
|
|
@ -204,14 +187,11 @@ export const estimateRows = (text: string, w: number, compact = false) => {
|
|||
|
||||
export const flat = (r: Record<string, string[]>) => Object.values(r).flat()
|
||||
|
||||
const COMPACT_NUMBER = new Intl.NumberFormat('en-US', {
|
||||
maximumFractionDigits: 1,
|
||||
notation: 'compact'
|
||||
})
|
||||
const COMPACT_NUMBER = new Intl.NumberFormat('en-US', { maximumFractionDigits: 1, notation: 'compact' })
|
||||
|
||||
export const fmtK = (n: number) => COMPACT_NUMBER.format(n).replace(/[KMBT]$/, s => s.toLowerCase())
|
||||
|
||||
export const pick = <T>(a: T[]) => a[Math.floor(Math.random() * a.length)]!
|
||||
|
||||
export const isPasteBackedText = (text: string): boolean =>
|
||||
export const isPasteBackedText = (text: string) =>
|
||||
/\[\[paste:\d+(?:[^\n]*?)\]\]|\[paste #\d+ (?:attached|excerpt)(?:[^\n]*?)\]/.test(text)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue