fix(cli): keep prompt stable and flush stale arrow-key input

This commit is contained in:
张涔熙 2026-02-08 10:38:32 +08:00
parent 438ec66fd8
commit 8b1ef77970
2 changed files with 76 additions and 1 deletions

View File

@ -1,7 +1,10 @@
"""CLI commands for nanobot.""" """CLI commands for nanobot."""
import asyncio import asyncio
import os
from pathlib import Path from pathlib import Path
import select
import sys
import typer import typer
from rich.console import Console from rich.console import Console
@ -18,6 +21,40 @@ app = typer.Typer(
console = Console() console = Console()
def _flush_pending_tty_input() -> None:
"""Drop unread keypresses typed while the model was generating output."""
try:
fd = sys.stdin.fileno()
if not os.isatty(fd):
return
except Exception:
return
try:
import termios
termios.tcflush(fd, termios.TCIFLUSH)
return
except Exception:
pass
try:
while True:
ready, _, _ = select.select([fd], [], [], 0)
if not ready:
break
if not os.read(fd, 4096):
break
except Exception:
return
def _read_interactive_input() -> str:
"""Read user input with a stable prompt for terminal line editing."""
console.print("[bold blue]You:[/bold blue] ", end="")
return input()
def version_callback(value: bool): def version_callback(value: bool):
if value: if value:
console.print(f"{__logo__} nanobot v{__version__}") console.print(f"{__logo__} nanobot v{__version__}")
@ -318,7 +355,8 @@ def agent(
async def run_interactive(): async def run_interactive():
while True: while True:
try: try:
user_input = console.input("[bold blue]You:[/bold blue] ") _flush_pending_tty_input()
user_input = _read_interactive_input()
if not user_input.strip(): if not user_input.strip():
continue continue

View File

@ -0,0 +1,37 @@
import builtins
import nanobot.cli.commands as commands
def test_read_interactive_input_uses_plain_input(monkeypatch) -> None:
captured: dict[str, object] = {}
def fake_print(*args, **kwargs):
captured["printed"] = args
captured["print_kwargs"] = kwargs
def fake_input(prompt: str = "") -> str:
captured["prompt"] = prompt
return "hello"
monkeypatch.setattr(commands.console, "print", fake_print)
monkeypatch.setattr(builtins, "input", fake_input)
value = commands._read_interactive_input()
assert value == "hello"
assert captured["prompt"] == ""
assert captured["print_kwargs"] == {"end": ""}
assert captured["printed"] == ("[bold blue]You:[/bold blue] ",)
def test_flush_pending_tty_input_skips_non_tty(monkeypatch) -> None:
class FakeStdin:
def fileno(self) -> int:
return 0
monkeypatch.setattr(commands.sys, "stdin", FakeStdin())
monkeypatch.setattr(commands.os, "isatty", lambda _fd: False)
commands._flush_pending_tty_input()