fix(state): durable _row_id adoption after rewind is opt-in (TUI) instead of every warm caller

On origin/main only the TUI copied durable `_row_id`s onto the installed warm
prefix (its clients address follow-ups by row); folding the loop into
`rewind_user_turn` made CLI /undo grow `_row_id` on a resumed history that never
had it. Live CLI rows already carry ids from the flush, so for them it was a
no-op, but a resumed transcript changed shape. The loop now runs only with
`adopt_row_ids=True`, which the TUI passes; the CLI history shape is unchanged.

Review follow-up on #109610.
This commit is contained in:
teknium1
2026-09-12 23:48:46 -07:00
committed by Teknium
parent f3b96e34c4
commit 82d03714ed
3 changed files with 25 additions and 4 deletions

View File

@@ -47,7 +47,7 @@ class SessionRewindMixin:
def rewind_user_turn(
self, session_id: str, user_ordinal: int, *, warm_history: Optional[List[Dict[str, Any]]] = None,
require_retryable: bool = False, require_composite: bool = False,
require_retryable: bool = False, require_composite: bool = False, adopt_row_ids: bool = False,
) -> RewindOutcome:
"""Rewind the active transcript to just before user turn ``user_ordinal`` (0 = oldest; negative counts
back from the newest and clamps to the oldest, so ``-n`` is ``/undo n``). ``warm_history`` (CLI/TUI):
@@ -55,7 +55,9 @@ class SessionRewindMixin:
transcript, else ``RuntimeError`` and nothing changes; its (richer) prefix is what gets installed.
``require_retryable``: the live payload must be losslessly replayable as text (``ValueError`` from
:func:`retryable_user_text` before any write). ``require_composite``: the target must be a compaction
carrier. Out-of-range / wrong-shape targets raise :class:`RewindTargetUnavailableError`."""
carrier. ``adopt_row_ids`` (TUI): copy durable ``_row_id`` identities onto the installed warm prefix so
clients can address follow-ups by row; the CLI leaves its history shape alone. Out-of-range /
wrong-shape targets raise :class:`RewindTargetUnavailableError`."""
from agent.context_compressor import (
_DB_PERSISTED_MARKER, history_before_user_originated_turn, retryable_user_text,
split_user_originated_turn)
@@ -103,7 +105,7 @@ class SessionRewindMixin:
raise RuntimeError("rewind did not retain its compaction handoff")
durable_prefix[-1].update({"_row_id": replacement_id, _DB_PERSISTED_MARKER: True})
prefix[-1] = durable_prefix[-1]
if prefix is not durable_prefix and len(prefix) == len(durable_prefix) and all(
if adopt_row_ids and prefix is not durable_prefix and len(prefix) == len(durable_prefix) and all(
warm.get("role") == durable_message.get("role")
and bool(warm.get("display_kind")) == bool(durable_message.get("display_kind"))
and _comparison_content(warm) == _comparison_content(durable_message)