Skip to content

fix(cli): use get_wch() for CJK/Unicode input in curses session browser - #40458

Open
liuhao1024 wants to merge 3 commits into
NousResearch:mainfrom
liuhao1024:fix/curses-cjk-input
Open

fix(cli): use get_wch() for CJK/Unicode input in curses session browser#40458
liuhao1024 wants to merge 3 commits into
NousResearch:mainfrom
liuhao1024:fix/curses-cjk-input

Conversation

@liuhao1024

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes CJK (Korean, Chinese, Japanese) and emoji input in the curses-based session browser (hermes sessions browse) and the shared curses menu. Previously, only printable ASCII characters (key codes 32–126) were accepted — all Unicode input was silently ignored.

Related Issue

Fixes #40446

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • hermes_cli/main.py: In _session_browse_picker, replaced stdscr.getch() with stdscr.get_wch() (with AttributeError fallback for platforms lacking wide-char support). Updated key comparisons to accept both integer codes (from getch()) and string characters (from get_wch()): Enter ("\n", "\r"), Esc ("\x1b"), Backspace ("\x7f"), quit ("q"). Added isinstance(key, str) and key.isprintable() branch for Unicode character input.
  • hermes_cli/curses_ui.py: In _run_curses_menu, replaced stdscr.getch() with get_wch() + fallback. In _handle_active_search_key, updated type annotation to int | str, and updated all key comparisons (Esc, Backspace, Ctrl+U, Enter, printable chars) to handle string keys from get_wch().
  • tests/hermes_cli/test_curses_cjk_input.py: 14 regression tests — 8 unit tests for _handle_active_search_key (Korean, Chinese, Japanese, emoji, ASCII fallback, non-printable rejection, Esc, special keys) and 6 integration tests for _session_browse_picker (Korean/Chinese/Japanese/emoji filtering, multi-char CJK search, getch() fallback).

How to Test

  1. Run pytest tests/hermes_cli/test_curses_cjk_input.py -xvs — all 14 tests should pass
  2. Run hermes sessions browse in a terminal with Korean/Chinese/Japanese input method enabled
  3. Type CJK characters — the filter should update and sessions should be filtered correctly
  4. Verify English input, Enter, Esc, Backspace, and arrow keys still work as before

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guideget_wch() is wrapped in try/except AttributeError for graceful fallback on platforms without wide-char curses support
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

Code Intelligence

  • Analyzed: hermes_cli/main.py:_session_browse_picker (session browser input loop), hermes_cli/curses_ui.py:_handle_active_search_key (shared menu search handler), hermes_cli/curses_ui.py:_run_curses_menu (shared menu key reading)
  • Blast radius: LOW — changes are isolated to curses input handling; fallback to getch() preserves existing behavior on all platforms
  • Related patterns: getch() returns integer key codes (0–255); get_wch() returns strings for Unicode characters and integers for special keys. All key comparisons must handle both types.

Replace stdscr.getch() with stdscr.get_wch() in _session_browse_picker
and _run_curses_menu to support CJK (Korean, Chinese, Japanese) and
emoji input. getch() only returns byte values (0-255), silently dropping
all Unicode characters. get_wch() returns proper Unicode strings.

Key comparisons updated to handle both integer codes (from getch()
fallback) and string characters (from get_wch()): Enter, Esc, Backspace,
Ctrl+U, and quit key. Fallback to getch() via AttributeError for
curses builds without wide-char support.

Fixes NousResearch#40446

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

Verdict: Approved

Replaces getch() with get_wch() in cli.py for proper CJK/Unicode character capture in the curses session browser. This is the correct fix for wide-character input on platforms that support ncurses wide-character functions.

Looks Good

  • Addresses a real Unicode pain point in the TUI
  • Change is contained to the input capture path
  • No regression for ASCII input — get_wch() handles both

Reviewed by Hermes Agent

@alt-glitch alt-glitch added type/bug Something isn't working comp/cli CLI entry point, hermes_cli/, setup wizard P3 Low — cosmetic, nice to have labels Jun 6, 2026
The PR replaced stdscr.getch() with stdscr.get_wch() for CJK/Unicode
support, but the test mock only intercepted getch(). The default
MagicMock.get_wch() returned a MagicMock object instead of raising
AttributeError, so the curses input loop never matched any key and
hung until the 30s pytest timeout.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for tracing the separate curses session-browser path; current main still has the reported ASCII-only input gate at hermes_cli/main.py:1083-1115.

Problems

  • In the shared searchable-menu path, this changes input to get_wch() but leaves the search opener as key == ord("/") (hermes_cli/curses_ui.py:501 in the PR context). get_wch() returns printable / as a string, so / is ignored and search cannot be activated. The same integer-only assumptions remain in _decode_menu_key.
  • tests/hermes_cli/test_curses_cjk_input.py tests _handle_active_search_key directly, but does not cover the _run_curses_menu loop receiving string keys from get_wch().

Suggested changes

  • Handle both "/" and ord("/"), and normalize or extend _decode_menu_key for string character keys.
  • Add an end-to-end mocked shared-menu search test using string get_wch() values.

Automated hermes-sweeper review.

Comment thread hermes_cli/curses_ui.py
@@ -475,7 +479,10 @@ def _draw(stdscr):
stdscr.refresh()

if use_search:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_wch() returns printable keys as strings. The following unchanged branch checks key == ord("/"), so / no longer opens search in this loop. Please normalize string/int keys here (and in _decode_menu_key) and add a loop-level regression test.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform area/sessions Session lifecycle, resume, persistence, history labels Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/sessions Session lifecycle, resume, persistence, history comp/cli CLI entry point, hermes_cli/, setup wizard P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: hermes sessions browse search filter ignores Korean/CJK input

4 participants