Stage 318: PR #1856 — fix: preserve pending turn during stale cleanup by @ai-ag2026

This commit is contained in:
nesquena-hermes
2026-05-08 15:01:48 +00:00
2 changed files with 41 additions and 1 deletions
+7 -1
View File
@@ -675,6 +675,7 @@ def _clear_stale_stream_state(session) -> bool:
with _get_session_agent_lock(session.session_id):
if getattr(session, "active_stream_id", None) != stream_id:
return False
_materialize_pending_user_turn_before_error(session)
session.active_stream_id = None
if hasattr(session, "pending_user_message"):
session.pending_user_message = None
@@ -1508,7 +1509,12 @@ from api.workspace import (
_workspace_blocked_roots,
)
from api.upload import handle_upload, handle_upload_extract, handle_transcribe
from api.streaming import _sse, _run_agent_streaming, cancel_stream
from api.streaming import (
_sse,
_run_agent_streaming,
cancel_stream,
_materialize_pending_user_turn_before_error,
)
from api.providers import get_providers, get_provider_quota, set_provider_key, remove_provider_key
from api.onboarding import (
apply_onboarding_setup,
+34
View File
@@ -366,6 +366,40 @@ def test_stream_error_pending_materialization_does_not_duplicate_eager_checkpoin
assert [m.get("role") for m in s.messages].count("user") == 1
def test_stale_stream_cleanup_materializes_pending_turn_before_clearing_state():
"""A zombie/stale stream repair must preserve the pending user prompt.
If the process dies after chat_start saved pending_user_message but before the
agent merges the user turn, /api/session stale cleanup must not clear that
pending field without first appending a durable user message.
"""
from api.routes import _clear_stale_stream_state
sid = "test_pending_error_d3_stale"
s = _make_session(
session_id=sid,
pending_msg="please make the GUI fully usable",
messages=[{"role": "assistant", "content": "previous answer"}],
)
s.pending_started_at = 1778187755.0
s.pending_attachments = [{"name": "visible-state.png"}]
# No matching STREAMS entry: this simulates a dead worker/server restart.
cleared = _clear_stale_stream_state(s)
assert cleared is True
assert s.active_stream_id is None
assert s.pending_user_message is None
assert s.messages[-1]["role"] == "user"
assert s.messages[-1]["content"] == "please make the GUI fully usable"
assert s.messages[-1]["timestamp"] == 1778187755
assert s.messages[-1]["attachments"] == [{"name": "visible-state.png"}]
reloaded = models.get_session(sid, metadata_only=False)
assert reloaded.messages[-1]["role"] == "user"
assert reloaded.messages[-1]["content"] == "please make the GUI fully usable"
# ── Structural guard: pin call sites of the materialize helper at error branches ──
def test_materialize_helper_called_immediately_before_error_path_clears():