hermes-agent/tui_gateway
ethernet 3133af8215
fix(desktop): prevent duplicate messages when verification candidates are persisted (#68149)
The display_history_prefix calculation used by session.resume's
_live_session_payload was display_history[:len(display) - len(raw)].
This assumed the model (repaired) history is always a suffix of the
display history — i.e., repair_message_sequence only removes messages
from the tail. That assumption broke when verification candidates
(finish_reason=verification_required) were persisted to state.db (#65919):

  - repair collapses consecutive assistant messages, removing the
    verification candidate from the MODEL history
  - the candidate stays in the DISPLAY history (it's real persisted content)
  - the length gap (gap = len(display) - len(raw)) counts BOTH ancestor
    messages AND repair-removed tip messages
  - the prefix = display[:gap] grabs the first N display messages, which
    are tip messages (not ancestors) when there are no compression ancestors
  - _live_session_payload concatenates prefix + model_history, duplicating
    the first N messages

On session 20260720_110036_a33889 (8 verification candidates), this
duplicated the first 8 messages in every warm-cache session.activate
response, producing visible duplicate user messages in the desktop.

Fix: add SessionDB.get_ancestor_display_prefix() which returns ONLY
genuine ancestor messages (rows where session_id != tip_session_id),
identified at the row level before _rows_to_conversation strips
session_id. Both resume paths (eager + deferred) now use this instead
of the length-slice heuristic.

Tests:
  - test_get_ancestor_display_prefix_single_session_returns_empty
  - test_get_ancestor_display_prefix_returns_ancestor_only_messages
  - Updated 12 mock DBs across test_protocol.py + test_tui_gateway_server.py
  - 848 passed (run_tests.sh), 0 regressions
2026-07-20 17:14:41 +00:00
..
__init__.py
_stdin_recovery.py fix: Windows guard, dedup recovery, profile-safe paths, clear SO_RCVTIMEO 2026-07-20 12:48:50 +05:30
compute_host.py fix(tui): recover from spurious stdin EOF caused by child O_NONBLOCK flip 2026-07-20 12:48:50 +05:30
entry.py fix: Windows guard, dedup recovery, profile-safe paths, clear SO_RCVTIMEO 2026-07-20 12:48:50 +05:30
event_publisher.py
git_probe.py
host_supervisor.py feat(dashboard): isolate turns in compute host (#65895) 2026-07-16 15:24:03 -04:00
loop_noise.py
project_tree.py fix(desktop): mirror Windows path identity in live overlay + WSL spelling 2026-07-12 04:33:05 -04:00
render.py
server.py fix(desktop): prevent duplicate messages when verification candidates are persisted (#68149) 2026-07-20 17:14:41 +00:00
slash_worker.py fix: Windows guard, dedup recovery, profile-safe paths, clear SO_RCVTIMEO 2026-07-20 12:48:50 +05:30
synthetic_turn.py feat(dashboard): isolate turns in compute host (#65895) 2026-07-16 15:24:03 -04:00
transport.py
ws.py fix(tui_gateway): prevent WS disconnect under GIL pressure 2026-06-30 03:11:13 -07:00