hermes - ✅(Solved) Fix Bug: TUI voice recording shortcut ignores voice.record_key [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#18994Fetched 2026-05-03 04:52:59
View on GitHub
Comments
0
Participants
1
Timeline
6
Reactions
0
Author
Participants
Timeline (top)
labeled ×4cross-referenced ×1referenced ×1

In the new hermes --tui, the voice recording shortcut is hardcoded to Ctrl+B / action-modifier+B and does not respect the configured voice.record_key value from config.yaml. Classic CLI does respect the config key.

Root Cause

The TUI frontend does not load or receive voice.record_key. Its key helper is hardcoded:

export const isVoiceToggleKey = (key, ch) =>
  (key.ctrl || isActionMod(key)) && ch.toLowerCase() === 'b'

Classic CLI, by contrast, reads load_config().get("voice", {}).get("record_key", "ctrl+b"), converts it to prompt-toolkit format, and registers @kb.add(_voice_key).

Fix Action

Fixed

PR fix notes

PR #19028: fix(tui): respect voice.record_key config instead of hardcoded Ctrl+B

Description (problem / solution / changelog)

What does this PR do?

Classic CLI loaded voice.record_key from config.yaml and bound the prompt-toolkit handler dynamically (cli.py rewrites ctrl+xc-x, ctrl+spacec-space, etc.). The new TUI hardcoded Ctrl+B everywhere — isVoiceToggleKey (input handler), /voice status ("Record key: Ctrl+B"), and /voice on ("Ctrl+B to start/stop recording"). A user who set voice.record_key: ctrl+o (or any other key, including named keys like ctrl+space) saw the documented config silently ignored — only Ctrl+B worked, the displayed shortcut lied about it.

Wire the configured key end-to-end through the existing channels:

  • Backend: voice.toggle action=status AND action=on/off responses now include record_key, sourced from cfg.voice.record_key (default ctrl+b).
  • Backend types: ConfigFullResponse.config.voice.record_key and VoiceToggleResponse.record_key.
  • Frontend parser/formatter: parseVoiceRecordKey() accepts single-character bindings (ctrl+b, alt+r) AND the named-key tokens that the CLI's prompt_toolkit binding accepts (ctrl+space, ctrl+enter/return, ctrl+tab, ctrl+escape/esc, ctrl+backspace/bs, ctrl+delete/del). Modifier aliases mirror common conventions (option, cmd, command, win, windows). Falls back to documented Ctrl+B for empty / unrecognised multi-character input so a typo never silently disables the shortcut. formatVoiceRecordKey() renders for status text in title case (Ctrl+Space, Ctrl+Enter).
  • Hydration: startup config.get full already runs; extract cfg.voice.record_key, parse, push into a new voiceRecordKey state, forward to input handler ctx. Mtime-poll path also re-applies so a config.yaml hand-edit takes effect on the next tick (matches existing display-option behaviour).
  • Slash command: /voice status and /voice on use formatVoiceRecordKey on the response's record_key instead of the hardcoded label.

ParsedVoiceRecordKey carries an optional named field; ch holds either a single character (back-compat) or the canonical named token. The runtime matcher dispatches on named before checking modifier shape — wrong key never reaches the modifier guard. Aliases collapse to one canonical name so ctrl+esc and ctrl+escape behave identically in both binding and display.

Related Issue

Fixes #18994

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • tui_gateway/server.pyvoice.toggle status + on/off branches include record_key in response (sourced from cfg.voice.record_key).
  • ui-tui/src/lib/platform.ts — new parseVoiceRecordKey, formatVoiceRecordKey, DEFAULT_VOICE_RECORD_KEY, ParsedVoiceRecordKey, VoiceRecordKeyMod, VoiceRecordKeyNamed. Internal _NAMED_KEY_ALIASES and _matchesNamedKey map config tokens to ink event-property checks. isVoiceToggleKey now takes a parsed ParsedVoiceRecordKey; default arg keeps existing call sites back-compat.
  • ui-tui/src/gatewayTypes.tsConfigVoiceConfig, ConfigFullResponse.config.voice, VoiceToggleResponse.record_key.
  • ui-tui/src/app/useConfigSync.ts — hydrate from config.get full; mtime poll also re-applies the parsed key.
  • ui-tui/src/app/useMainApp.tsvoiceRecordKey state, pass through to useConfigSync and the input handler ctx.
  • ui-tui/src/app/interfaces.tsInputHandlerContext.voice.recordKey.
  • ui-tui/src/app/useInputHandlers.ts — pass voice.recordKey into isVoiceToggleKey.
  • ui-tui/src/app/slash/commands/session.ts — render configured key in /voice status and /voice on.
  • ui-tui/src/__tests__/platform.test.ts — coverage for the parser (single-char + every supported named token + each alias variant + fall-back-to-Ctrl+B for empty/typo'd input), the formatter (single-char and named-key title-case rendering), and the runtime matcher (configured-letter binding, alt/meta terminal-protocol parity, named-key event-property dispatch including modifier-mismatch negatives, and back-compat default-arg behaviour).

How to Test

Letter binding (most common)

  1. voice: { record_key: ctrl+o } in ~/.hermes/config.yaml.
  2. hermes --tui/voice status → "Record key: Ctrl+O" (was: Ctrl+B).
  3. /voice on → "Ctrl+O to start/stop recording".
  4. Press Ctrl+O → recording toggles. Press Ctrl+B → no effect.

Named-key binding

  1. voice: { record_key: ctrl+space } in ~/.hermes/config.yaml.
  2. hermes --tui/voice status → "Record key: Ctrl+Space".
  3. Press Ctrl+Space → recording toggles. (Also try ctrl+enter, ctrl+tab, ctrl+esc, ctrl+backspace, ctrl+delete — and the aliases return, escape, bs, del.)

Tests

  1. cd ui-tui && npm test → 559/559 pass. npm run type-check → clean.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits
  • I searched for existing PRs
  • My PR contains only changes related to this fix
  • I've run pytest tests/ -q and the touched suites pass
  • I've added tests for my changes
  • I've tested on my platform: macOS 15.x

Documentation & Housekeeping

  • I've updated relevant documentation — config-side behaviour was already documented (tips.py:322 already says "configurable via voice.record_key in config.yaml — not just Ctrl+B"); this PR closes the gap between docs and behaviour. No new docs needed.
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A (uses existing voice.record_key)
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A
  • I've considered cross-platform impact — checked: ink event flags (key.return, key.tab, key.escape, key.backspace, key.delete) are surfaced consistently across xterm-style and kitty-style terminals; the alt+meta dual-match in the modifier dispatch covers the protocol split. Ctrl/Cmd dual-accept on macOS preserves existing muscle memory.
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A

Changed files

  • tui_gateway/server.py (modified, +17/-1)
  • ui-tui/src/__tests__/platform.test.ts (modified, +133/-0)
  • ui-tui/src/app/interfaces.ts (modified, +2/-0)
  • ui-tui/src/app/slash/commands/session.ts (modified, +10/-3)
  • ui-tui/src/app/useConfigSync.ts (modified, +35/-6)
  • ui-tui/src/app/useInputHandlers.ts (modified, +1/-1)
  • ui-tui/src/app/useMainApp.ts (modified, +4/-2)
  • ui-tui/src/gatewayTypes.ts (modified, +6/-1)
  • ui-tui/src/lib/platform.ts (modified, +200/-8)

Code Example

voice:
  record_key: ctrl+o

---

export const isVoiceToggleKey = (key, ch) =>
  (key.ctrl || isActionMod(key)) && ch.toLowerCase() === 'b'
RAW_BUFFERClick to expand / collapse

Hermes TUI: voice recording shortcut ignores voice.record_key

Summary

In the new hermes --tui, the voice recording shortcut is hardcoded to Ctrl+B / action-modifier+B and does not respect the configured voice.record_key value from config.yaml. Classic CLI does respect the config key.

Reproduction

  1. Set a non-default voice recording shortcut, for example:
voice:
  record_key: ctrl+o
  1. Start classic hermes and run /voice on.
  2. Start hermes --tui and run /voice on.
  3. Try the configured shortcut and Ctrl+B in both interfaces.

Expected

Both classic CLI and new TUI should use the configured voice.record_key for starting/stopping voice recording, and /voice status should display the configured key.

Actual

Classic CLI reads voice.record_key and binds the prompt-toolkit key handler dynamically.

The new TUI still displays and handles Ctrl+B:

  • /voice status prints Record key: Ctrl+B.
  • /voice on prints Ctrl+B to start/stop recording.
  • ui-tui/src/lib/platform.ts implements isVoiceToggleKey() as modifier + b only.
  • ui-tui/src/app/useInputHandlers.ts calls voiceRecordToggle() only when isVoiceToggleKey(key, ch) matches.

Root cause

The TUI frontend does not load or receive voice.record_key. Its key helper is hardcoded:

export const isVoiceToggleKey = (key, ch) =>
  (key.ctrl || isActionMod(key)) && ch.toLowerCase() === 'b'

Classic CLI, by contrast, reads load_config().get("voice", {}).get("record_key", "ctrl+b"), converts it to prompt-toolkit format, and registers @kb.add(_voice_key).

Suggested fix

Expose the configured record key to the TUI, either during startup/config hydration or through the voice.toggle/voice.status RPC response, then use one shared parser/normalizer for supported key strings such as ctrl+x and alt+x. The TUI should also use the configured display value in /voice status and /voice on output instead of hardcoded Ctrl+B.

extent analysis

TL;DR

The TUI frontend should be updated to load and respect the configured voice.record_key from config.yaml instead of hardcoding it to Ctrl+B.

Guidance

  • Expose the configured record key to the TUI during startup or through RPC responses.
  • Implement a shared parser/normalizer for supported key strings, such as ctrl+x and alt+x.
  • Update the TUI to use the configured display value in /voice status and /voice on output.
  • Review the isVoiceToggleKey function to ensure it correctly handles the configured record key.

Example

// Example of how to parse and normalize key strings
const parseKey = (keyStr: string) => {
  const parts = keyStr.split('+');
  const modifier = parts[0];
  const key = parts[1];
  return { modifier, key };
};

// Example of how to use the parsed key in isVoiceToggleKey
export const isVoiceToggleKey = (key, ch, configuredKey) => {
  const parsedKey = parseKey(configuredKey);
  return (key === parsedKey.modifier) && ch.toLowerCase() === parsedKey.key;
}

Notes

The suggested fix requires changes to the TUI frontend to load and respect the configured record key. The example provided is a minimal illustration of how to parse and normalize key strings, but the actual implementation may vary depending on the specific requirements of the application.

Recommendation

Apply workaround by updating the TUI frontend to load and respect the configured voice.record_key from config.yaml, as this will allow the application to correctly handle user-configured voice recording shortcuts.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - ✅(Solved) Fix Bug: TUI voice recording shortcut ignores voice.record_key [1 pull requests, 1 participants]