mirror of
https://github.com/NCBM/plyngent.git
synced 2026-11-07 15:37:55 +08:00
cli/repl: keep second Ctrl+C inside the REPL
This commit is contained in:
@@ -22,6 +22,12 @@ def _echo_user(text: str) -> None:
|
||||
click.echo(text)
|
||||
|
||||
|
||||
def _echo_interrupted() -> None:
|
||||
click.echo()
|
||||
click.secho("interrupted", fg="yellow")
|
||||
click.echo()
|
||||
|
||||
|
||||
async def run_repl(state: ReplState) -> None:
|
||||
"""Interactive chat loop with readline editing, history, and Tab completion."""
|
||||
setup_readline(state)
|
||||
@@ -47,7 +53,11 @@ async def run_repl(state: ReplState) -> None:
|
||||
continue
|
||||
|
||||
if entry.startswith("/"):
|
||||
cont = await handle_slash(state, entry)
|
||||
try:
|
||||
cont = await handle_slash(state, entry)
|
||||
except KeyboardInterrupt:
|
||||
_echo_interrupted()
|
||||
continue
|
||||
if not cont:
|
||||
break
|
||||
if state.pending_user_text is not None:
|
||||
@@ -56,6 +66,8 @@ async def run_repl(state: ReplState) -> None:
|
||||
_echo_user(text)
|
||||
try:
|
||||
_ = await run_user_text_with_retries(state.agent, text)
|
||||
except KeyboardInterrupt:
|
||||
_echo_interrupted()
|
||||
finally:
|
||||
state.expire_yolo_once()
|
||||
continue
|
||||
@@ -63,5 +75,7 @@ async def run_repl(state: ReplState) -> None:
|
||||
_echo_user(entry)
|
||||
try:
|
||||
_ = await run_user_text_with_retries(state.agent, entry)
|
||||
except KeyboardInterrupt:
|
||||
_echo_interrupted()
|
||||
finally:
|
||||
state.expire_yolo_once()
|
||||
|
||||
+26
-17
@@ -131,20 +131,36 @@ def _echo_turn_usage(agent: ChatAgent) -> None:
|
||||
click.secho(f"[{'; '.join(parts)}]", fg="bright_black")
|
||||
|
||||
|
||||
async def _wait_for_retry(attempt: int, max_retries: int, wait: float) -> bool:
|
||||
click.secho(
|
||||
f"auto-retry {attempt}/{max_retries} in {wait:g}s (Ctrl+C to cancel; then /retry later)",
|
||||
fg="yellow",
|
||||
)
|
||||
def _echo_cancel_lines(*lines: str) -> None:
|
||||
"""Print post-cancel lines, treating a further Ctrl+C as benign.
|
||||
|
||||
After a turn-cancel the asyncio SIGINT handler is removed, so a second
|
||||
Ctrl+C during this output would otherwise raise KeyboardInterrupt and
|
||||
exit the REPL instead of returning to the prompt.
|
||||
"""
|
||||
try:
|
||||
for line in lines:
|
||||
if line:
|
||||
click.secho(line, fg="yellow")
|
||||
else:
|
||||
click.echo()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
|
||||
async def _wait_for_retry(attempt: int, max_retries: int, wait: float) -> bool:
|
||||
try:
|
||||
click.secho(
|
||||
f"auto-retry {attempt}/{max_retries} in {wait:g}s (Ctrl+C to cancel; then /retry later)",
|
||||
fg="yellow",
|
||||
)
|
||||
ok = await sleep_cancellable(wait)
|
||||
except KeyboardInterrupt:
|
||||
ok = False
|
||||
if not ok:
|
||||
click.secho("auto-retry cancelled; use /retry to try again", fg="yellow")
|
||||
click.echo()
|
||||
_echo_cancel_lines("auto-retry cancelled; use /retry to try again", "")
|
||||
return False
|
||||
click.secho(f"retrying ({attempt}/{max_retries})…", fg="yellow")
|
||||
_echo_cancel_lines(f"retrying ({attempt}/{max_retries})…")
|
||||
return True
|
||||
|
||||
|
||||
@@ -170,17 +186,10 @@ async def run_turn_with_retries(
|
||||
await run_cancellable(render_events(current()))
|
||||
except asyncio.CancelledError:
|
||||
# Do not auto-retry cancelled turns — user intent was stop, not retry.
|
||||
click.echo()
|
||||
click.secho(
|
||||
"cancelled; user message kept — use /retry to try again",
|
||||
fg="yellow",
|
||||
)
|
||||
click.echo()
|
||||
_echo_cancel_lines("", "cancelled; user message kept — use /retry to try again", "")
|
||||
return False
|
||||
except KeyboardInterrupt:
|
||||
click.echo()
|
||||
click.secho("interrupted", fg="yellow")
|
||||
click.echo()
|
||||
_echo_cancel_lines("", "interrupted", "")
|
||||
return False
|
||||
except Exception as exc: # noqa: BLE001 — surface and optionally retry
|
||||
click.secho(f"error: {exc}", fg="red")
|
||||
|
||||
Reference in New Issue
Block a user