fix(tui-gateway): merge agent output on model-switch history_version mismatch (#76870)

When a model switch occurs mid-turn, `_append_model_switch_marker()`
appends a marker to session history and increments `history_version`.
The turn completion guard then sees `current_version != history_version`
and discards all agent output — producing empty assistant messages in
the session DB.

Detect when the only history mutation during the turn was one or more
model-switch markers.  In that case, merge the agent's new messages
into the current history (which now contains the marker) instead of
discarding them.  Genuine desyncs (undo/compress/retry) still surface
the warning as before.

Fixes #76870
This commit is contained in:
JonthanaHanh 2026-08-03 10:05:45 +07:00 committed by kshitij
parent 75901a295d
commit f9ed58e6ac
1 changed files with 30 additions and 17 deletions

View File

@ -9689,24 +9689,37 @@ def _run_prompt_submit(
session["history"] = result["messages"]
session["history_version"] = history_version + 1
else:
# History mutated externally during the turn
# (undo/compress/retry/rollback now guard on
# session.running, but this is the defensive
# backstop for any path that slips past).
# Surface the desync rather than silently
# dropping the agent's output — the UI can
# show the response and warn that it was
# not persisted.
print(
f"[tui_gateway] prompt.submit: history_version mismatch "
f"(expected={history_version} current={current_version}) — "
f"agent output NOT written to session history",
file=sys.stderr,
)
status_note = (
"History changed during this turn — the response above is visible "
"but was not saved to session history."
# History mutated externally during the turn.
# Check if the only mutation was a model-switch
# marker inserted mid-turn (#76870). If so the
# agent output is still valid — merge it into the
# current history that now contains the marker.
current_history = list(session["history"])
added = current_history[len(history):]
model_switch_only = (
len(added) >= 1
and all(_is_model_switch_marker(e) for e in added)
)
if model_switch_only:
new_messages = result["messages"][len(history):]
session["history"] = current_history + new_messages
session["history_version"] = current_version + 1
else:
# Genuine desync (undo/compress/retry/rollback).
# Surface the desync rather than silently
# dropping the agent's output — the UI can
# show the response and warn that it was
# not persisted.
print(
f"[tui_gateway] prompt.submit: history_version mismatch "
f"(expected={history_version} current={current_version}) — "
f"agent output NOT written to session history",
file=sys.stderr,
)
status_note = (
"History changed during this turn — the response above is visible "
"but was not saved to session history."
)
# If auto-compression fired inside run_conversation(), agent.session_id
# may have rotated. Sync session_key before downstream title/goal/finalize