refactor: move SafeFileHistory to module level + add regression tests

- Promote _SafeFileHistory to module-level SafeFileHistory for testability
- Add 5 regression tests: surrogates, normal text, emoji, mixed CJK, multi-surrogates

Made-with: Cursor
This commit is contained in:
Xubin Ren
2026-04-07 13:57:34 +08:00
committed by Xubin Ren
parent 64bd7234b3
commit 075bdd5c3c
2 changed files with 58 additions and 10 deletions
+14 -10
View File
@@ -33,6 +33,19 @@ from rich.table import Table
from rich.text import Text
from nanobot import __logo__, __version__
class SafeFileHistory(FileHistory):
"""FileHistory subclass that sanitizes surrogate characters on write.
On Windows, special Unicode input (emoji, mixed-script) can produce
surrogate characters that crash prompt_toolkit's file write.
See issue #2846.
"""
def store_string(self, string: str) -> None:
safe = string.encode("utf-8", errors="surrogateescape").decode("utf-8", errors="replace")
super().store_string(safe)
from nanobot.cli.stream import StreamRenderer, ThinkingSpinner
from nanobot.config.paths import get_workspace_path, is_default_workspace
from nanobot.config.schema import Config
@@ -118,17 +131,8 @@ def _init_prompt_session() -> None:
history_file = get_cli_history_path()
history_file.parent.mkdir(parents=True, exist_ok=True)
# Wrap FileHistory to sanitize surrogate characters on write.
# Without this, special Unicode input (emoji, mixed-script) crashes
# prompt_toolkit's history file write on Windows with UnicodeEncodeError.
# See issue #2846.
class _SafeFileHistory(FileHistory):
def store_string(self, string: str) -> None:
safe = string.encode("utf-8", errors="surrogateescape").decode("utf-8", errors="replace")
super().store_string(safe)
_PROMPT_SESSION = PromptSession(
history=_SafeFileHistory(str(history_file)),
history=SafeFileHistory(str(history_file)),
enable_open_in_editor=False,
multiline=False, # Enter submits (single line mode)
)