hermes-agent/ui-tui/src/lib/liveProgress.ts
Brooklyn Nicholson b1c49d5e73 chore(tui): /clean recent perf work — KISS/DRY pass
24 files, -319 LoC. Behaviour preserved, 369/369 tests green.

- hermes-ink caches: shared lruEvict helper for the four parallel LRU
  caches (stringWidth, wrapText, sliceAnsi, lineWidth); touch-on-read
  stays inlined per cache; tightened output.ts skip-slice fast path.
- wheelAccel: trimmed provenance header, collapsed env parsing, ternary
  dispatch in computeWheelStep.
- perfPane: folded ensureLogDir into once-flag, spread-with-overrides
  for fastPath/phases instead of full rebuilds.
- env: extracted truthy() (used 4×).
- virtualHeights: collapsed user/diff/slash height bumps; trail+todos
  estimate.
- useInputHandlers: scrollIdleTimer cleanup on unmount, ?? undefined
  shorthand.
- useMainApp: dropped dead liveTailVisible IIFE and liveProgress
  indirection.
- appLayout, markdown, messageLine, entry: vertical rhythm, dropped
  narration comments, inlined one-shot vars.
- fix: empty catch blocks → /* best-effort */ for no-empty lint.
2026-04-26 20:38:47 -05:00

79 lines
2.1 KiB
TypeScript

import type { Msg, TodoItem } from '../types.js'
export const countPendingTodos = (todos: readonly TodoItem[]) =>
todos.filter(todo => todo.status === 'in_progress' || todo.status === 'pending').length
export const isTodoDone = (todos: readonly TodoItem[]) =>
todos.length > 0 && todos.every(todo => todo.status === 'completed' || todo.status === 'cancelled')
export const isToolShelfMessage = (msg: Msg | undefined) =>
Boolean(msg?.kind === 'trail' && !msg.text && !msg.thinking?.trim() && msg.tools?.length)
export const canHoldToolShelf = (msg: Msg | undefined) =>
Boolean(msg?.kind === 'trail' && !msg.text && (msg.thinking?.trim() || msg.tools?.length))
export const mergeToolShelfInto = (target: Msg, source: Msg): Msg => ({
...target,
tools: [...(target.tools ?? []), ...(source.tools ?? [])]
})
const isBarrierMessage = (msg: Msg | undefined) => {
if (!msg) {
return true
}
// Assistant text, user input, intro/panel rows all terminate the shelf.
if (msg.kind === 'intro' || msg.kind === 'panel' || msg.kind === 'diff') {
return true
}
if (msg.role && msg.role !== 'system') {
return true
}
if (msg.text) {
return true
}
return false
}
const isToolCarryingTrail = (msg: Msg | undefined) => Boolean(msg?.kind === 'trail' && !msg.text && msg.tools?.length)
export const appendToolShelfMessage = (prev: readonly Msg[], msg: Msg): Msg[] => {
if (!isToolShelfMessage(msg)) {
return [...prev, msg]
}
let fallbackHolder: number | null = null
for (let index = prev.length - 1; index >= 0; index--) {
const candidate = prev[index]
if (isToolCarryingTrail(candidate)) {
const next = [...prev]
next[index] = mergeToolShelfInto(candidate!, msg)
return next
}
if (fallbackHolder === null && canHoldToolShelf(candidate)) {
fallbackHolder = index
}
if (isBarrierMessage(candidate)) {
break
}
}
if (fallbackHolder !== null) {
const next = [...prev]
next[fallbackHolder] = mergeToolShelfInto(prev[fallbackHolder]!, msg)
return next
}
return [...prev, msg]
}