hermes-agent/ui-tui
Teknium 597615ade4
fix(ci): make tests, workflows, and attribution reliable under load (#66373)
* feat(attribution): conflict-free contributor mappings via contributors/emails/ directory

The AUTHOR_MAP dict in scripts/release.py was a merge-conflict magnet:
every concurrent salvage PR appended entries to the same lines of the
same file, so parallel PRs re-conflicted on every merge to main.

New system: one file per email under contributors/emails/ — filename is
the commit-author email, first non-comment line is the GitHub login.
File additions never conflict, so any number of PRs can add mappings
concurrently.

- scripts/release.py: AUTHOR_MAP is now LEGACY_AUTHOR_MAP (frozen)
  merged with the directory at import time (directory wins). All
  existing consumers (resolve_author, contributor_audit.py) unchanged.
- scripts/add_contributor.py: idempotent CLI to add a mapping; refuses
  conflicting reassignments (incl. against the legacy map), validates
  email/login shapes.
- contributor-check.yml: attribution gate now accepts a mapping file OR
  a legacy entry; failure message prints the exact add_contributor
  command. Also auto-resolves bare <login>@users.noreply.github.com
  emails is intentionally NOT added (kept id+login form only, matching
  previous behavior).
- contributor_audit.py: guidance now points at add_contributor.py.
- tests/scripts/test_contributor_map.py: 12 tests covering loader,
  merge precedence, CLI idempotency/conflict/validation, subprocess E2E.

* feat(ci): one-shot per-file flake retry in the parallel test runner

A failing test FILE is re-run once in a fresh subprocess. Pass-on-retry
counts as green but is loudly reported in a '⚠ FLAKY' summary section
(with both attempts' output preserved) so the flake gets fixed instead
of eating a full-run rerun. Deterministic failures fail both attempts —
regressions cannot be laundered green.

- --file-retries N / HERMES_TEST_FILE_RETRIES (default 1, 0 disables)
- E2E verified: simulated first-run-fail flake goes green with banner;
  deterministic failure still exits 1; retries=0 restores old behavior.

This converts the dominant CI failure mode (one timing-sensitive test
flaking a 4600-test shard, requiring a manual 10-minute rerun and an
agent triage loop) into a self-healing retry that costs one file's
runtime.

* test(approval): loosen wall-clock perf bounds 0.15s -> 2.0s

These guard against catastrophic regex backtracking (seconds-to-minutes
class), but 0.15s is within scheduler-stall noise on loaded shared CI
runners — test_max_accepted_separator_free_input_is_fast failed a CI
shard this week on runner load alone. 2.0s still catches the regression
class with zero flake surface.

* fix(ci): job timeouts everywhere + retries on all network installs

Reliability pass over every workflow:
- timeout-minutes on all 21 jobs that lacked one (a hung job previously
  burned the 6-hour default runner budget)
- ./.github/actions/retry wrapped around every network-fetching install
  that lacked it: pip installs (deploy-site, skills-index), npm ci
  (deploy-site website, upload_to_pypi web + ui-tui), uv sync (docker
  test deps). Deterministic build steps (npm run build) deliberately
  NOT retried — split into separate steps so a real build failure fails
  fast instead of retrying 3x.

* docs(agents): document the file-retry flake policy

* fix(ci): curl retries on deploy hook + skills-index probe

* fix(ci): kill the remaining transient-failure classes in workflows + Dockerfile

From the workflow reliability audit:
- tests.yml: duration-cache restore had NO restore-keys while saves use
  run_id-suffixed keys — the cache never matched once, so LPT slicing
  always ran blind and unbalanced slices pushed heavy files toward the
  per-file timeout. One-line restore-keys fixes slice balancing.
- Label gates (lint ci-reviewed, supply-chain mcp-catalog-reviewed):
  'gh pr view || true' turned an API blip into 'label absent' → false
  BLOCKING failure. Now 3x retry, and API failure is reported as an API
  failure instead of a missing label.
- detect-changes action: compare API retried before failing open (was
  silently running all lanes on any blip).
- uv-lockfile-check: 'uv lock --check' resolves against PyPI — retried
  so registry blips don't read as 'lockfile stale'.
- docker.yml merge job: imagetools create retried (Docker Hub eventual
  consistency on just-pushed digests).
- Dockerfile: apt-get Acquire::Retries=3; s6-overlay ADDs converted to
  curl --retry 3 (ADD cannot retry; checksums still enforced); npm
  --fetch-retries=5; playwright chromium fetch retried 3x.
- Advisory artifact uploads (per-slice durations, ci-timings report)
  get continue-on-error so an artifact-service blip can't fail a green
  test slice.

* fix(tests): kill the two root-cause flakes — leaking pre-warm timer + env-dependent provider list

- test_tui_gateway_server.py: session.create / non-eager session.resume
  arm a 50ms threading.Timer (_schedule_agent_build) that outlives its
  test and fires into the NEXT test's _make_agent mock, racily
  corrupting captured state (the recurring session_resume shard
  failures). Replaced the per-test whack-a-mole stub with a module-wide
  autouse fixture; the 3 worker-lifecycle tests that genuinely need the
  deferred build opt back in via @pytest.mark.real_agent_prewarm (new
  marker in pyproject).
- test_api_key_providers.py: PROVIDER_ENV_VARS is now derived from the
  live PROVIDER_REGISTRY instead of a hand-list that had drifted
  (missing HF_TOKEN / DEEPINFRA_API_KEY) — resolve_provider('auto')
  tests failed on any machine with HF_TOKEN exported. E2E-verified with
  HF_TOKEN/DEEPINFRA_API_KEY set: 42/42 pass.

* test: de-flake 30 timing-sensitive test files for loaded CI runners

Root-cause fixes from the flake audit (session-DB mining + repo sweep):

Event-based sync instead of sleep-sync:
- title_generator: mock sets threading.Event, wait(10) replaces
  sleep(0.3) hoping the daemon thread got scheduled
- docker zombie_reaping / profile_gateway: poll-for-state helpers
  replace fixed 1-3s sleeps (s6 transitions + SIGCHLD reaping are async)
- process_registry tree test: select()-bounded readline replaces an
  unbounded blocking read (parent wedge now fails THIS test with a clear
  message instead of an opaque rc=124 file kill); SIGTERM grace 1s->2s
  (the 1s partition window mid-interpreter-startup is how a child PID
  escaped the live-system guard in CI)

Timeout raises (loaded 8-way-sliced runners see ~5s scheduling floors;
all of these complete in ms-to-1s when healthy so the raises cost
nothing on green runs):
- subprocess/thread waits <= 2s raised to 10-15s across mcp_tool,
  mcp_circuit_breaker, mcp_reconnect_retry_reset, mcp_parked_self_probe,
  mcp_cancelled_error_propagation, registry, clarify_gateway, interrupt,
  voice_cli_integration, docker_environment, session_store_lock_io,
  planned_stop_watcher, cli_interrupt_subagent, thread_scoped_output
  (joins now also assert not is_alive() so stragglers fail loudly)
- wall-clock discrimination ceilings loosened where the guarded hang is
  10x larger: local_background_child_hang 4s->10s, interrupt_cleanup
  setup 5s->20s + pgid-exit 30s->60s, mcp_stability grandchild spinup
  5s->15s, protocol/gil-starvation fast-handler 0.5s->2s,
  iso_certify_seam 1.5s->5s, wait_for_mcp_discovery 0.1s->1s
- narrow assertion windows widened: honcho first-turn wait 0.4..0.65 ->
  0.25..2.0 (property is bounded-not-hung, not an exact wall-clock);
  compression fork-lock TTL 1s->3s (12 refresh chances per lease);
  compression-lock expiry margins symmetric (ttl 0.05->0.5, sleep 1.0)
- telegram hung-DNS bound 1.0->1.4 (fake hang is 1.5s — must stay under)

* fix(tests): repair indentation from de-flake batch edit

* fix(tests): harden env isolation and replace remaining sleep-sync races

The full 42k-test run and complete npm check surfaced three more classes:

- Environment isolation: local ~/.honcho defaultHost and SSH_* variables
  leaked into Python/TUI tests. Pin the default Honcho host in the
  hermetic fixture, isolate the one fallback test from ~/.honcho, and
  blank SSH_* around terminalSetup tests. This flipped 20 false failures
  back to deterministic behavior on developer machines.
- Background-thread sleep-sync: Honcho async writer tests patched
  time.sleep globally, then busy-polled with that same mocked sleep. Under
  full-suite load the poller could starve the writer. Each test now waits
  on an Event emitted by the exact flush/retry transition; 30/30 passed
  under 15-way contention.
- Desktop streaming: the test slept 80ms and assumed a 500ms timer could
  not fire before its assertion. A loaded runner descheduled the test for
  >500ms and both chunks arrived. Producer controls now gate second-chunk
  and completion transitions explicitly.

Also make file-retry observability complete: a self-healed flaky file now
prints BOTH attempts' full output in the FLAKY summary. Two behavioral
runner tests prove pass-on-retry is green+loud+traceback-preserving, while
a deterministic failure remains red.

* refactor(ci): use gh bot pat, better retries

refactor(ci): use retry action for PR label fetch
the retry action now captures stdout as a step output, so it can serve
double duty: retry + output capture for commands like 'gh pr view' whose
result must be consumed by later steps.

Retry action gains:
- 'stdout' output (heredoc-delimited to preserve newlines)
- tee to temp file so stdout still streams to the job log
- step id 'retry' for output reference

Both lint.yml and supply-chain-audit.yml now use the retry action
directly with 'command: gh pr view ...' and read
steps.<id>.outputs.stdout.

ci: use AUTOFIX_BOT_PAT for all gh CLI / GitHub API auth

Replace secrets.GITHUB_TOKEN and github.token with
secrets.AUTOFIX_BOT_PAT across all workflows and composite actions
that use the gh CLI or GitHub API. The PAT has consistent permissions
across fork PRs (where GITHUB_TOKEN is read-only), avoids API rate
limit sharing with the default token, and is already used by
js-autofix.yml for the same reasons.

19 sites swapped across 9 files:
- lint.yml (3): label fetch, comment post/edit, comment update
- supply-chain-audit.yml (5): scan, critical comment, unbounded dep
  comment, label fetch, mcp-catalog comment
- lockfile-diff.yml (1): PR comment post/update
- skills-index-freshness.yml (1): issue creation on degraded probe
- skills-index.yml (2): index build, trigger deploy workflow
- upload_to_pypi.yml (2): release view poll, release upload
- ci.yml (1): timings report
- deploy-site.yml (2): skills index crawl
- detect-changes/action.yml (1): compare API call

---------

Co-authored-by: ethernet <arilotter@gmail.com>
2026-07-17 20:55:24 +00:00
..
packages/hermes-ink fix(tui): recover mouse tracking without a resize via DECRQM watchdog (#66080) 2026-07-16 22:25:50 -07:00
scripts docs(tui): clarify why @hermes/ink is aliased to source in build.mjs 2026-07-01 02:10:32 -07:00
src fix(ci): make tests, workflows, and attribution reliable under load (#66373) 2026-07-17 20:55:24 +00:00
.gitignore fix(ui-tui): harden TUI error handling, model validation, command UX parity, and gateway lifecycle 2026-04-13 18:29:24 -05:00
eslint.config.mjs refactor(lint): hoist shared eslint + prettier config to root 2026-07-16 01:42:02 +05:30
package.json fix(js): fix long-time broken tests 2026-07-13 17:22:17 -04:00
README.md fix(tui): dismiss expired sensitive prompts 2026-07-10 14:45:43 -04:00
tsconfig.build.json change(tooling): typecheck in CI, update ts to 6 2026-06-10 11:59:34 -04:00
tsconfig.json change(tooling): typecheck in CI, update ts to 6 2026-06-10 11:59:34 -04:00
vitest.config.ts chore: uptick 2026-04-15 23:29:00 -05:00

Hermes TUI

React + Ink terminal UI for Hermes. TypeScript owns the screen. Python owns sessions, tools, model calls, and most command logic.

hermes --tui

What runs

The client entrypoint is src/entry.tsx. It exits early if stdin is not a TTY, starts GatewayClient, then renders App.

GatewayClient spawns:

python -m tui_gateway.entry

Interpreter resolution order is: HERMES_PYTHONPYTHON$VIRTUAL_ENV/bin/python./.venv/bin/python./venv/bin/pythonpython3 (or python on Windows).

The transport is newline-delimited JSON-RPC over stdio:

ui-tui/src                  tui_gateway/
-----------                 -------------
entry.tsx                   entry.py
  -> GatewayClient            -> request loop
  -> App                      -> server.py RPC handlers

stdin/stdout: JSON-RPC requests, responses, events
stderr: captured into an in-memory log ring

Malformed stdout lines are treated as protocol noise and surfaced as gateway.protocol_error. Stderr lines become gateway.stderr. Neither writes directly into the terminal.

Running it

From the repo root, the normal path is:

hermes --tui

The CLI expects ui-tui/dist/entry.js to exist, or the whole source code available in which to run npm install and npm run dev.

cd ui-tui
npm install

Local package commands:

npm run dev
npm start
npm run build
npm run lint
npm run fmt
npm run fix

Tests use vitest:

npm test         # single run
npm run test:watch

App model

src/app.tsx is the center of the UI. Heavy logic is split into src/app/:

  • src/app/createGatewayEventHandler.ts — maps gateway events to state updates
  • src/app/createSlashHandler.ts — local slash command dispatch
  • src/app/useComposerState.ts — draft, multiline buffer, queue editing
  • src/app/useInputHandlers.ts — keypress routing
  • src/app/useMainApp.ts — top-level composition hook: wires all sub-hooks, manages transcript history, session polling, and exposes props consumed by app.tsx
  • src/app/useSessionLifecycle.ts — session create / resume / activate / close and visible-history reset
  • src/app/useSubmission.ts — message send, shell exec (!cmd), inline interpolation ({!cmd}), and busy-input-mode dispatch (queue / steer / interrupt)
  • src/app/turnController.ts — stateful class that drives the turn lifecycle: buffers streaming deltas, manages tool/reasoning state, handles interrupt and message-complete transitions
  • src/app/turnStore.ts — nanostore for turn state (streaming text, tools, reasoning, subagents, todos, activity trail)
  • src/app/useConfigSync.ts — fetches config.get full on session start and polls config mtime every 5 s; applies display settings and triggers MCP reload on change
  • src/app/useLongRunToolCharms.ts — fires ambient activity messages for tools running longer than 8 s
  • src/app/overlayStore.ts / src/app/uiStore.ts — nanostores for overlay and UI state
  • src/app/delegationStore.ts — nanostore for subagent spawning caps and overlay accordion state
  • src/app/spawnHistoryStore.ts — in-memory ring (last 10) of finished subagent fan-out snapshots; populated at turn end for /replay
  • src/app/inputSelectionStore.ts — nanostore exposing the active text-input selection handle
  • src/app/gatewayContext.tsx — React context for the gateway client
  • src/app/gatewayRecovery.ts — pure function that decides whether to respawn and resume after a gateway crash, with a 3-attempt / 60 s budget
  • src/app/setupHandoff.ts — launches external hermes setup, suspends Ink while it runs, opens a new session on success
  • src/app/scroll.ts — scrolls the viewport while keeping the text selection anchor in sync
  • src/app/interfaces.ts — internal interfaces (ComposerActions, GatewayRpc, etc.)

Slash command subsystem (src/app/slash/)

  • types.tsSlashCommand interface and SlashRunCtx execution context (gateway rpc, transcript helpers, session refs, stale-guard)
  • registry.ts — assembles SLASH_COMMANDS from all command files in registration order (core → billing → credits → session → ops → setup → debug) and exposes findSlashCommand(name) for case-insensitive lookup
  • commands/core.ts — general TUI commands
  • commands/billing.ts/billing: manage Nous terminal billing — buy credits, auto-reload, limits
  • commands/credits.ts/credits
  • commands/session.ts — session and agent commands
  • commands/ops.ts — operations commands
  • commands/setup.ts/setup
  • commands/debug.ts/heapdump, /mem

The top-level app.tsx composes these into the Ink tree with Static transcript output, a live streaming assistant row, prompt overlays, queue preview, status rule, input line, and completion list.

State managed at the top level includes:

  • transcript and streaming state
  • queued messages and input history
  • session lifecycle
  • tool progress and reasoning text
  • prompt flows for approval, clarify, sudo, and secret input
  • slash command routing
  • tab completion and path completion
  • theme state from gateway skin data

The UI renders as a normal Ink tree with Static transcript output, a live streaming assistant row, prompt overlays, queue preview, status rule, input line, and completion list.

The intro panel is driven by session.info and rendered through branding.tsx.

Hotkeys and interactions

Current input behavior is split across app.tsx, components/textInput.tsx, and the prompt/picker components.

Main chat input

Key Behavior
Enter Submit the current draft
empty Enter twice If queued messages exist and the agent is busy, interrupt the current run. If queued messages exist and the agent is idle, send the next queued message
Shift+Enter / Alt+Enter Insert a newline in the current draft
\ + Enter Append the line to the multiline buffer (fallback for terminals without modifier support)
Ctrl+C Interrupt active run, or clear the current draft, or exit if nothing is pending
Ctrl+D Exit
Cmd/Ctrl+G / Alt+G Open $EDITOR with the current draft (use Alt+G in VSCode/Cursor — they bind the primary keystroke to Find Next)
Ctrl+L New session (same as /clear)
Ctrl+V / Alt+V Paste text first, then fall back to image/path attachment when applicable
Tab Apply the active completion
Up/Down Cycle completions if the completion list is open; otherwise edit queued messages first, then walk input history
Left/Right Move the cursor
modified Left/Right Move by word when the terminal sends Ctrl or Meta with the arrow key
Home / Ctrl+A Start of line
End / Ctrl+E End of line
Backspace Delete the character to the left of the cursor
Delete Delete the character to the right of the cursor
modified Backspace Delete the previous word
modified Delete Delete the next word
Ctrl+W Delete the previous word
Ctrl+U Delete from the cursor back to the start of the line
Ctrl+K Delete from the cursor to the end of the line
Meta+B / Meta+F Move by word
!cmd Run a shell command through the gateway
{!cmd} Inline shell interpolation before send; queued drafts keep the raw text until they are sent

Notes:

  • Tab only applies completions when completions are present and you are not in multiline mode.
  • Queue/history navigation only applies when you are not in multiline mode.
  • PgUp / PgDn are left to the terminal emulator; the TUI does not handle them.

Prompt and picker modes

Context Keys Behavior
approval prompt Up/Down, Enter Move and confirm the selected approval choice
approval prompt o, s, a, d Quick-pick once, session, always, deny
approval prompt Esc, Ctrl+C Deny
clarify prompt with choices Up/Down, Enter Move and confirm the selected choice
clarify prompt with choices single-digit number Quick-pick the matching numbered choice
clarify prompt with choices Enter on "Other" Switch into free-text entry
clarify free-text mode Enter Submit typed answer
sudo / secret prompt Enter Submit typed value
sudo / secret prompt Ctrl+C Cancel by sending an empty response
resume picker Up/Down, Enter Move and resume the selected session
resume picker 1-9 Quick-pick one of the first nine visible sessions
resume picker Esc, Ctrl+C Close the picker

Notes:

  • Clarify free-text mode and masked prompts use ink-text-input, so text editing there follows the library's default bindings rather than components/textInput.tsx.
  • When a blocking prompt is open, the main chat input hotkeys are suspended.
  • Clarify mode has no dedicated cancel shortcut in the current client. Sudo and secret prompts only expose Ctrl+C cancellation from the app-level blocked handler.

Interaction rules

  • Plain text entered while the agent is busy is queued instead of sent immediately.
  • Slash commands and !cmd do not queue; they execute immediately even while a run is active.
  • Queue auto-drains after each assistant response, unless a queued item is currently being edited.
  • Up/Down prioritizes queued-message editing over history. History only activates when there is no queue to edit.
  • Queued drafts keep their original !cmd and {!cmd} text while you edit them. Shell commands and interpolation run when the queued item is actually sent.
  • If you load a queued item into the input and resubmit plain text, that queue item is replaced, removed from the queue preview, and promoted to send next. If the agent is still busy, the edited item is moved to the front of the queue and sent after the current run completes.
  • Completion requests are debounced by 60 ms. Input starting with / uses complete.slash. A trailing token that starts with ./, ../, ~/, /, or @ uses complete.path.
  • Text pastes are inserted inline directly into the draft. Nothing is newline-flattened.
  • Cmd/Ctrl+G (or Alt+G in VSCode/Cursor, which intercept the primary keystroke for Find Next) writes the current draft, including any multiline buffer, to a temp file, suspends Ink, launches $EDITOR, then restores the TUI and submits the saved text if the editor exits cleanly.
  • Input history is stored in ~/.hermes/.hermes_history or under HERMES_HOME.

Rendering

Assistant output is rendered in one of two ways:

  • if the payload already contains ANSI, messageLine.tsx prints it directly
  • otherwise components/markdown.tsx renders a small Markdown subset into Ink components

The Markdown renderer handles headings, lists, block quotes, tables, fenced code blocks, diff coloring, inline code, emphasis, links, and plain URLs.

Tool/status activity is shown in a live activity lane. Transcript rows stay focused on user/assistant turns.

Prompt flows

The Python gateway can pause the main loop and request structured input:

  • approval.request: allow once, allow for session, allow always, or deny
  • clarify.request: pick from choices or type a custom answer
  • sudo.request: masked password entry
  • secret.request: masked value entry for a named env var
  • session.list: used by SessionPicker for /resume

These are stateful UI branches in app.tsx, not separate screens.

Commands

The following commands are handled directly by the TUI client. Unrecognized commands fall through to the Python gateway via slash.exec and command.dispatch.

Core (core.ts)

/help, /quit (alias /exit), /update, /clear (alias /new), /compact, /copy, /paste, /details (alias /detail), /statusbar (alias /sb), /queue (alias /q), /logs, /history, /save, /undo, /retry, /steer, /mouse (alias /scroll), /status, /title, /fortune, /redraw, /terminal-setup

Billing (billing.ts)

/billing — manage Nous terminal billing — buy credits, auto-reload, limits

Session (session.ts)

/model, /sessions (aliases /switch, /session, /resume), /background (aliases /bg, /btw), /image, /personality, /compress, /branch (alias /fork), /voice, /skin, /indicator, /yolo, /reasoning, /fast, /busy, /verbose, /usage

Ops (ops.ts)

/stop, /reload-mcp (alias /reload_mcp), /reload, /browser, /rollback, /agents (alias /tasks), /replay, /replay-diff, /skills, /reload-skills (alias /reload_skills), /plugins, /tools

Credits (credits.ts)

/credits — Nous credit balance and browser top-up

Setup (setup.ts)

/setup — launches external hermes setup wizard, suspends Ink while it runs

Debug (debug.ts)

/heapdump, /mem — V8 memory diagnostics


Anything not matched above falls through to:

  1. slash.exec
  2. command.dispatch

That lets Python own aliases, plugins, skills, and registry-backed commands without duplicating the logic in the TUI.

Event surface

Primary event types the client handles today:

Event Payload
gateway.ready { skin? }
skin.changed { skin }
session.info session metadata for banner + tool/skill panels
message.start start assistant streaming
message.delta { text, rendered? }
message.complete { text, rendered?, usage, status }
thinking.delta { text }
reasoning.delta { text, verbose? }
reasoning.available { text, verbose? }
status.update { kind, text }
notification.show { id, key, kind, level, text, ttl_ms? }
notification.clear { key }
tool.start { tool_id, name, context?, args_text? }
tool.generating { name }
tool.progress { name, preview }
tool.complete { tool_id, name, error?, summary?, duration_s?, inline_diff?, todos? }
clarify.request { question, choices?, request_id }
approval.request { command, description, allow_permanent? }
sudo.request { request_id }
sudo.expire { request_id } clears a timed-out sudo prompt
secret.request { prompt, env_var, request_id }
secret.expire { request_id } clears a timed-out secret prompt
background.complete { task_id, text }
billing.step_up.verification { verification_url, user_code }
review.summary { text }
browser.progress { message }
voice.status { state }
voice.transcript { text, no_speech_limit? }
subagent.spawn_requested { subagent_id?, task_index, goal?, depth?, parent_id? }
subagent.start { subagent_id?, task_index, goal?, depth?, parent_id? }
subagent.thinking { text }
subagent.tool { tool_name?, tool_preview?, text? }
subagent.progress { text }
subagent.complete { status, summary?, text?, duration_seconds? }
error { message }
gateway.stderr synthesized from child stderr
gateway.protocol_error synthesized from malformed stdout
gateway.start_timeout { cwd?, python?, stderr_tail? }

Theme model

The client starts with DEFAULT_THEME from theme.ts, then merges in gateway skin data from gateway.ready.

Current branding overrides:

  • agent name
  • prompt symbol
  • welcome text
  • goodbye text

Current color overrides:

  • banner title, accent, border, body, dim
  • label, ok, error, warn

branding.tsx uses those values for the logo, session panel, and update notice.

File map

ui-tui/
  packages/hermes-ink/   forked Ink renderer (local dep)
  src/
    entry.tsx            TTY gate + render()
    app.tsx              top-level Ink tree, composes src/app/*
    gatewayClient.ts     child process + JSON-RPC bridge
    gatewayTypes.ts      gateway event and RPC response type definitions
    theme.ts             theme colors and skin merge
    banner.ts            ASCII art renderer (parses Rich color tags)
    types.ts             shared client-side types (ActiveTool, Msg, etc.)

    app/
      createGatewayEventHandler.ts  event → state mapping
      createSlashHandler.ts         local slash dispatch
      delegationStore.ts            nanostore for subagent spawning caps and overlay accordion state
      gatewayContext.tsx            React context for gateway client
      gatewayRecovery.ts            crash-recovery budget: respawn+resume capped to 3 attempts / 60 s
      inputSelectionStore.ts        nanostore exposing the active text-input selection handle
      interfaces.ts                 internal interfaces (ComposerActions, GatewayRpc, etc.)
      overlayStore.ts               nanostores for overlay state
      scroll.ts                     viewport scroll with text-selection anchor sync
      setupHandoff.ts               launches external hermes setup, suspends Ink while it runs
      spawnHistoryStore.ts          ring buffer of finished subagent fan-out snapshots
      turnController.ts             stateful turn lifecycle driver (streaming, tools, reasoning)
      turnStore.ts                  nanostore for turn state (streaming, tools, reasoning, subagents)
      uiStore.ts                    nanostores for UI flags (busy, sid, mouseTracking, etc.)
      useComposerState.ts           draft + multiline buffer + queue editing
      useConfigSync.ts              config polling and MCP reload on mtime change
      useInputHandlers.ts           keypress routing
      useLongRunToolCharms.ts       ambient activity messages for tools running longer than 8 s
      useMainApp.ts                 top-level composition hook
      useSessionLifecycle.ts        session create / resume / activate / close
      useSubmission.ts              message send, shell exec, interpolation, busy-input-mode dispatch

      slash/
        types.ts                    SlashCommand interface and SlashRunCtx execution context
        registry.ts                 SLASH_COMMANDS assembly and findSlashCommand lookup
        commands/
          billing.ts                /billing — manage Nous terminal billing
          core.ts                   general TUI commands
          credits.ts                /credits
          debug.ts                  /heapdump, /mem
          ops.ts                    operations commands
          session.ts                session and agent commands
          setup.ts                  /setup wizard

    components/
      activeSessionSwitcher.tsx  active session switch overlay
      agentsOverlay.tsx          subagent delegation overlay
      appChrome.tsx              status bar, input row, completions
      appLayout.tsx              top-level layout composition
      appOverlays.tsx            overlay routing (pickers, prompts)
      billingOverlay.tsx         billing overlay
      branding.tsx               banner + session summary
      fpsOverlay.tsx             FPS debug overlay
      helpHint.tsx               contextual help hint
      markdown.tsx               Markdown-to-Ink renderer
      maskedPrompt.tsx           masked input for sudo / secrets
      messageLine.tsx            transcript rows
      modelPicker.tsx            model switch picker
      overlayControls.tsx        shared overlay control buttons
      pluginsHub.tsx             plugins hub overlay
      prompts.tsx                approval + clarify flows
      queuedMessages.tsx         queued input preview
      skillsHub.tsx              skills hub overlay
      streamingAssistant.tsx     live streaming assistant row
      streamingMarkdown.tsx      streaming Markdown renderer
      textInput.tsx              custom line editor
      themed.tsx                 theme-aware wrapper
      thinking.tsx               spinner, reasoning, tool activity
      todoPanel.tsx              todo list panel

    config/
      env.ts                     environment variable resolution and Termux/mouse defaults
      limits.ts                  paste size, live-render and history limits
      timing.ts                  streaming batch and debounce timing constants

    content/
      charms.ts                  ambient activity strings for long-running tools
      faces.ts                   agent face / kaomoji pool
      fortunes.ts                /fortune quote pool
      hotkeys.ts                 platform-aware hotkey display strings
      placeholders.ts            rotating input placeholder strings
      setup.ts                   setup-required panel content
      verbs.ts                   tool activity verb map (browser → browsing, etc.)

    domain/
      blockLayout.ts             block layout and lead-gap helpers
      details.ts                 details visibility mode resolution (hidden/collapsed/expanded)
      messages.ts                message formatting and transcript helpers
      paths.ts                   cwd shortening and path display helpers
      providers.ts               provider display name helpers
      roles.ts                   message role color and label helpers
      slash.ts                   slash command parsing and TUI session model flag
      usage.ts                   token usage zero value and helpers
      viewport.ts                viewport height estimation helpers

    hooks/
      useCompletion.ts           tab completion (slash + path)
      useGitBranch.ts            current git branch via child_process execFile
      useInputHistory.ts         persistent history navigation
      useQueue.ts                queued message management
      useVirtualHistory.ts       virtual list scroll and height tracking

    lib/
      circularBuffer.ts          fixed-size generic ring buffer
      clipboard.ts               clipboard read / write via child_process
      editor.ts                  $EDITOR launch, PATH resolution, and Ink suspend
      emoji.ts                   emoji and variation selector width helpers
      externalCli.ts             external CLI subprocess launcher
      externalLink.ts            open URLs in the system browser
      forceTruecolor.ts          24-bit truecolor override before chalk imports
      fpsStore.ts                Ink frame FPS tracker nanostore
      fuzzy.ts                   lightweight fuzzy subsequence scorer
      gracefulExit.ts            clean shutdown with failsafe timeout
      history.ts                 persistent input history (read/append to disk)
      inputMetrics.ts            input width and wrap metrics
      liveProgress.ts            todo helpers and tool-shelf message assembly
      mathUnicode.ts             best-effort LaTeX → Unicode for inline math
      memory.ts                  V8 heap snapshot and diagnostics helpers
      memoryMonitor.ts           automatic heap-dump trigger on high usage
      messages.ts                transcript message append helpers
      openExternalUrl.ts         platform-aware URL opener (macOS/Linux/Windows)
      osc52.ts                   OSC 52 terminal clipboard copy sequence
      parentLog.ts               append-only log to ~/.hermes/tui-parent.log
      perfPane.tsx               FPS / render perf overlay pane
      platform.ts                platform-aware keybinding and SSH detection helpers
      precisionWheel.ts          high-precision scroll wheel with sticky-frame budget
      prompt.ts                  composer prompt text helpers (Termux-safe)
      reasoning.ts               reasoning tag detection and split helpers
      rpc.ts                     JSON-RPC result and command dispatch helpers
      subagentTree.ts            subagent tree flattening and aggregate helpers
      syntax.ts                  syntax token types and theme-aware highlighting
      terminalModes.ts           terminal mode reset sequences (kitty, mouse, etc.)
      terminalParity.ts          VSCode-like terminal detection and hint helpers
      terminalSetup.ts           IDE keybinding config file install helpers
      termux.ts                  Termux platform detection helpers
      text.ts                    text helpers, ANSI detection, tool trail builders
      todo.ts                    todo item tone and display helpers
      viewportStore.ts           viewport height nanostore via ScrollBoxHandle
      virtualHeights.ts          virtual list row height estimation
      wheelAccel.ts              scroll wheel acceleration state machine

    protocol/
      interpolation.ts           {!cmd} inline shell interpolation regex and helpers
      paste.ts                   bracketed paste snippet token regex

    types/
      hermes-ink.d.ts            type declarations for @hermes/ink

    __tests__/                   vitest suite

Related Python side:

tui_gateway/
  entry.py               stdio entrypoint
  server.py              RPC handlers and session logic
  render.py              optional rich/ANSI bridge
  slash_worker.py        persistent HermesCLI subprocess for slash commands