fix: restore terminal state on Ctrl+C exit in agent interactive mode
This commit is contained in:
parent
eb2fbf80da
commit
25e17717c2
@ -3,6 +3,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import atexit
|
import atexit
|
||||||
import os
|
import os
|
||||||
|
import signal
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import select
|
import select
|
||||||
import sys
|
import sys
|
||||||
@ -29,6 +30,7 @@ _READLINE = None
|
|||||||
_HISTORY_FILE: Path | None = None
|
_HISTORY_FILE: Path | None = None
|
||||||
_HISTORY_HOOK_REGISTERED = False
|
_HISTORY_HOOK_REGISTERED = False
|
||||||
_USING_LIBEDIT = False
|
_USING_LIBEDIT = False
|
||||||
|
_SAVED_TERM_ATTRS = None # original termios settings, restored on exit
|
||||||
|
|
||||||
|
|
||||||
def _flush_pending_tty_input() -> None:
|
def _flush_pending_tty_input() -> None:
|
||||||
@ -67,9 +69,27 @@ def _save_history() -> None:
|
|||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def _restore_terminal() -> None:
|
||||||
|
"""Restore terminal to its original state (echo, line buffering, etc.)."""
|
||||||
|
if _SAVED_TERM_ATTRS is None:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
import termios
|
||||||
|
termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, _SAVED_TERM_ATTRS)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _enable_line_editing() -> None:
|
def _enable_line_editing() -> None:
|
||||||
"""Enable readline for arrow keys, line editing, and persistent history."""
|
"""Enable readline for arrow keys, line editing, and persistent history."""
|
||||||
global _READLINE, _HISTORY_FILE, _HISTORY_HOOK_REGISTERED, _USING_LIBEDIT
|
global _READLINE, _HISTORY_FILE, _HISTORY_HOOK_REGISTERED, _USING_LIBEDIT, _SAVED_TERM_ATTRS
|
||||||
|
|
||||||
|
# Save terminal state before readline touches it
|
||||||
|
try:
|
||||||
|
import termios
|
||||||
|
_SAVED_TERM_ATTRS = termios.tcgetattr(sys.stdin.fileno())
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
history_file = Path.home() / ".nanobot" / "history" / "cli_history"
|
history_file = Path.home() / ".nanobot" / "history" / "cli_history"
|
||||||
history_file.parent.mkdir(parents=True, exist_ok=True)
|
history_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
@ -422,6 +442,16 @@ def agent(
|
|||||||
_enable_line_editing()
|
_enable_line_editing()
|
||||||
console.print(f"{__logo__} Interactive mode (Ctrl+C to exit)\n")
|
console.print(f"{__logo__} Interactive mode (Ctrl+C to exit)\n")
|
||||||
|
|
||||||
|
# input() runs in a worker thread that can't be cancelled.
|
||||||
|
# Without this handler, asyncio.run() would hang waiting for it.
|
||||||
|
def _exit_on_sigint(signum, frame):
|
||||||
|
_save_history()
|
||||||
|
_restore_terminal()
|
||||||
|
console.print("\nGoodbye!")
|
||||||
|
os._exit(0)
|
||||||
|
|
||||||
|
signal.signal(signal.SIGINT, _exit_on_sigint)
|
||||||
|
|
||||||
async def run_interactive():
|
async def run_interactive():
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
@ -433,6 +463,8 @@ def agent(
|
|||||||
response = await agent_loop.process_direct(user_input, session_id)
|
response = await agent_loop.process_direct(user_input, session_id)
|
||||||
console.print(f"\n{__logo__} {response}\n")
|
console.print(f"\n{__logo__} {response}\n")
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
_save_history()
|
||||||
|
_restore_terminal()
|
||||||
console.print("\nGoodbye!")
|
console.print("\nGoodbye!")
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user