Files
hermes-webui/tests/test_5749_anchor_scene_client_prefix_dedupe.py
T
52c962cf7d Release: Transparent Stream multi-segment prefix dedupe (#6189, @ai-ag2026) (#6217)
* fix(transparent-stream): drop stale final-answer prefix row in multi-segment settle (#5749 follow-up)

A turn with interim assistant messages (prose interleaved with tool calls)
could show the beginning of the final answer TWICE after watching it stream:
once as a settled anchor-scene prose row (the live-token accumulator's last
throttled snapshot) and once as the real assistant segment. The duplicate
persisted until reload.

Root cause: #5758 suppresses the accumulator row only when it sits "after the
last tool row", but _completeSettledAnchorSceneForTurn appends the settled
per-message tool rows AFTER the projected live rows — those re-list tools that
ran EARLIER in the turn, pushing the boundary past the final segment's
accumulator so the guard never fired. The stale prefix snapshot then survived
into the persisted scene and rendered above the settled answer.

Fix: judge final-segment eligibility against the LIVE projection's own
chronology — a live-prose row belongs to the final segment iff no PROJECTED
tool row follows it. Pre-tool narration that happens to prefix the final
answer stays protected (existing #5758 regression tests still pass), and a
new regression test pins the multi-segment shape.

Verified end-to-end by replaying the captured run journal of an affected
session through the real SSE live handlers in headless Chromium: duplicate
before (answer prefix visible twice after settle), gone after; fresh-reload
rendering unchanged.

Rollback: revert this commit; behavior returns to pre-fix (duplicate prefix
row after live-settle of multi-segment turns).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* CHANGELOG: transparent-stream multi-segment prefix dedupe (#6189, @ai-ag2026)

---------

Co-authored-by: ai-ag2026 <m.fuechtenkoetter@posteo.de>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-07-17 21:51:22 -07:00

48 lines
2.2 KiB
Python

from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
MESSAGES_JS = (ROOT / "static" / "messages.js").read_text(encoding="utf-8")
def _function_body(src: str, name: str) -> str:
marker = f"function {name}"
start = src.index(marker)
brace = src.index("{", start)
depth = 0
for idx in range(brace, len(src)):
if src[idx] == "{":
depth += 1
elif src[idx] == "}":
depth -= 1
if depth == 0:
return src[brace + 1 : idx]
raise AssertionError(f"{name} body not found")
def test_settled_scene_keys_live_token_prefix_dedupe_to_final_answer_identity():
settle_body = _function_body(MESSAGES_JS, "_completeSettledAnchorSceneForTurn")
final_overlap_body = _function_body(MESSAGES_JS, "_anchorSceneRowLooksLikeFinalAnswer")
# The final-segment boundary must come from the LIVE projection's own
# chronology (projectedRows), not the combined orderedRows: the settled
# per-message tool rows appended there re-list tools that ran earlier in
# the turn and would push the boundary past the final segment's live-prose
# accumulator (the #5758 multi-segment gap).
assert "lastProjectedToolIndex=projectedRows.reduce" in settle_body
assert "finalSegmentLiveProseRows" in settle_body
assert "rowIsLiveTokenFinalPrefix(row,textKey,finalSegmentEligible)" in settle_body
assert "rowHasNonLiveDuplicate" not in settle_body
assert "_anchorSceneRowLooksLikeFinalAnswer(textKey,finalKey)" in settle_body
assert "(shorter/longer)>=0.9" in final_overlap_body
def test_render_scene_passes_final_segment_eligibility_to_live_prefix_guard():
ui_js = (ROOT / "static" / "ui.js").read_text(encoding="utf-8")
render_body = _function_body(ui_js, "_renderSettledAnchorSceneTransparentForMessage")
row_body = _function_body(ui_js, "_anchorSceneTransparentNodeForRow")
assert "const lastNonTerminalWorkRowIndex=_anchorSceneLastNonTerminalWorkRowIndex(rows);" in render_body
assert "liveTokenFinalPrefixEligible:idx>lastNonTerminalWorkRowIndex" in render_body
assert "opts&&opts.liveTokenFinalPrefixEligible&&_anchorSceneLiveTokenFinalPrefix" in row_body