mirror of
https://github.com/NCBM/plyngent.git
synced 2026-11-07 23:47:54 +08:00
cli: reset auto-continue at turn boundaries
This commit is contained in:
@@ -381,6 +381,9 @@ async def _run_chat( # noqa: C901, PLR0912, PLR0915 — chat orchestration
|
||||
|
||||
set_directory_access_confirm_hook(None, instance=state_obj.instance_state)
|
||||
clear_process_access(instance=state_obj.instance_state)
|
||||
from plyngent.cli.limits import reset_auto_continue
|
||||
|
||||
reset_auto_continue()
|
||||
if state_obj.mcp_manager is not None:
|
||||
await state_obj.mcp_manager.aclose()
|
||||
await state_obj.instance_state.shutdown()
|
||||
|
||||
@@ -10,6 +10,7 @@ import click
|
||||
|
||||
from plyngent.cli.display import render_events
|
||||
from plyngent.cli.interrupt import allow_task_cancel, set_sigint_reinstall
|
||||
from plyngent.cli.limits import reset_auto_continue_turn
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import AsyncIterator, Callable, Coroutine
|
||||
@@ -184,7 +185,23 @@ async def run_turn_with_retries(
|
||||
further attempts use ``agent.retry`` so the user message is not duplicated.
|
||||
|
||||
Ctrl+C cancels the in-flight task; user message stays in DB for ``/retry``.
|
||||
|
||||
The ``yyy`` limit auto-continue is turn-scoped: cleared before and after the
|
||||
turn, so a retry of the same turn keeps it but the next user turn prompts.
|
||||
"""
|
||||
reset_auto_continue_turn()
|
||||
try:
|
||||
return await _run_turn_with_retries(agent, starter=starter, delays=delays)
|
||||
finally:
|
||||
reset_auto_continue_turn()
|
||||
|
||||
|
||||
async def _run_turn_with_retries(
|
||||
agent: ChatAgent,
|
||||
*,
|
||||
starter: Callable[[], AsyncIterator[AgentEvent]],
|
||||
delays: tuple[float, ...],
|
||||
) -> bool:
|
||||
max_retries = len(delays)
|
||||
attempt = 0
|
||||
current: Callable[[], AsyncIterator[AgentEvent]] = starter
|
||||
|
||||
@@ -1018,11 +1018,17 @@ def btw_cmd(
|
||||
else:
|
||||
await run_cancellable(side)
|
||||
|
||||
from plyngent.cli.limits import reset_auto_continue_turn
|
||||
|
||||
# Aside is its own exchange: a ``yyy`` from the main turn must not leak in.
|
||||
reset_auto_continue_turn()
|
||||
try:
|
||||
_await(_run())
|
||||
except Exception as exc: # noqa: BLE001 — surface side-turn failures
|
||||
click.secho(f"error: btw failed: {exc}", fg="red")
|
||||
return
|
||||
finally:
|
||||
reset_auto_continue_turn()
|
||||
click.secho("(btw done — main session unchanged)", fg="bright_black")
|
||||
|
||||
|
||||
|
||||
@@ -108,6 +108,12 @@ class ReplState:
|
||||
set_verbose_tool_results(self.verbose)
|
||||
set_markdown_enabled(self.markdown_enabled)
|
||||
|
||||
def _reset_limit_auto_continue(self) -> None:
|
||||
"""Drop a ``yyy`` skip on session change (it is turn-scoped)."""
|
||||
from plyngent.cli.limits import reset_auto_continue_turn
|
||||
|
||||
reset_auto_continue_turn()
|
||||
|
||||
def _workspace_key(self) -> str:
|
||||
key = normalize_workspace(self.workspace)
|
||||
if key is None:
|
||||
@@ -658,6 +664,7 @@ class ReplState:
|
||||
return False
|
||||
|
||||
async def new_session(self, name: str = "chat") -> None:
|
||||
self._reset_limit_auto_continue()
|
||||
session = await self.memory.create_session(
|
||||
name=name,
|
||||
workspace=self.workspace,
|
||||
@@ -696,6 +703,7 @@ class ReplState:
|
||||
"""Load a session; on workspace mismatch, prompt keep / rebind / abort."""
|
||||
from plyngent.cli.limits import prompt_workspace_mismatch
|
||||
|
||||
self._reset_limit_auto_continue()
|
||||
row = await self.memory.get_session(session_id)
|
||||
if row is None:
|
||||
msg = f"session not found: {session_id}"
|
||||
@@ -733,6 +741,7 @@ class ReplState:
|
||||
|
||||
async def resume_latest_or_new(self, name: str = "chat") -> str:
|
||||
"""Resume most recently updated session for this workspace, or create one."""
|
||||
self._reset_limit_auto_continue()
|
||||
latest = await self.memory.get_latest_session(workspace=self.workspace)
|
||||
if latest is None:
|
||||
await self.new_session(name=name)
|
||||
|
||||
@@ -30,6 +30,8 @@ from plyngent.memory import MemoryStore
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import AsyncIterator
|
||||
|
||||
from plyngent.agent import AgentEvent
|
||||
|
||||
|
||||
def test_default_retry_delays_schedule() -> None:
|
||||
assert DEFAULT_MAX_AUTO_RETRIES == 10
|
||||
@@ -296,6 +298,43 @@ async def test_second_ctrl_c_during_retry_announce_stays_benign(
|
||||
assert await _wait_for_retry(1, 3, 0.01) is True
|
||||
|
||||
|
||||
async def test_turn_boundaries_reset_auto_continue() -> None:
|
||||
from plyngent.cli.limits import (
|
||||
auto_continue_enabled,
|
||||
prompt_continue_limit,
|
||||
reset_auto_continue,
|
||||
)
|
||||
from plyngent.prompting import temporary_backend
|
||||
from tests.test_prompting import ScriptedBackend
|
||||
|
||||
reset_auto_continue()
|
||||
try:
|
||||
with temporary_backend(ScriptedBackend(["yyy"])):
|
||||
assert prompt_continue_limit("limit") is True
|
||||
assert auto_continue_enabled() is True
|
||||
|
||||
store = await MemoryStore.open(DatabaseConfig())
|
||||
try:
|
||||
session = await store.create_session(name="t")
|
||||
client = FlakyClient(fail_times=0)
|
||||
agent = ChatAgent(client, model="m", memory=store, session_id=session.sid)
|
||||
seen: list[bool] = []
|
||||
|
||||
async def starter() -> AsyncIterator[AgentEvent]:
|
||||
seen.append(auto_continue_enabled())
|
||||
async for event in agent.run("hi"):
|
||||
yield event
|
||||
|
||||
ok = await run_turn_with_retries(agent, starter=starter, delays=())
|
||||
assert ok is True
|
||||
assert seen == [False] # cleared before the turn starts
|
||||
assert auto_continue_enabled() is False # cleared after the turn ends
|
||||
finally:
|
||||
await store.close()
|
||||
finally:
|
||||
reset_auto_continue()
|
||||
|
||||
|
||||
async def test_manual_retry_after_exhausted(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
async def no_wait(_seconds: float) -> bool:
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user