test_issue6867 extracts _restoreSettledSession into a bare harness where
the module-level _messagesTruncated/_oldestIdx lets are absent; a bare
read threw ReferenceError and the whole recovery path died before it
could restore. Use typeof guards like the surrounding paging restores.
The semantics are unchanged in the real page (both are always declared).
Two data-integrity blockers from round-3 review (nesquena-hermes 2026-09-19):
1. [CORE] A successful /compress returns the full transcript, but
_applyManualCompressionResult kept the bounded preflight's paging
signals (_messagesTruncated/_oldestIdx). Render now restores them
before renderMessages(), same ordering as settle/cancel paths.
2. [SILENT] _restoreSettledSession preservation used
(prefix || suffix), so repeated identical turns (colliding
_messageIdentityKey) made BOTH comparisons succeed and the prefix
branch spliced at the wrong offset - silently dropping rows and
duplicating others. The server truncation signal now decides the
strategy: truncated => suffix-exclusive, untruncated => prefix.
Tests: node behavioral driver covering /compress paging restore,
repeated-turn settle (no drop/dup, authoritative offset), and
gap-free msg_before paging after bounded recovery.
Bounded settle/cancel reloads return the latest 30 messages, not a
prefix of the visible window. The shorter-terminal preserve path only
compared prefixes, so a long session dropped the Connection-interrupted
marker. Anchor persist also ran before _oldestIdx was refreshed from
the bounded response, writing the scene against the wrong transcript
row.
- Treat a suffix match like a prefix match when preserving the terminal
recovery marker
- Refresh _messagesTruncated / _oldestIdx before attaching/persisting
the projected scene on settle, cancel, and apperror
- Document the msg_limit=30 / msg_limit=all contract
- Add runtime + source-order regression tests
Fixes the #7310 stall class: six frontend recovery paths (offline/bfcache
refreshSession, stream-end settle, cancel sync, /compress preflight, /retry,
/undo) issued bare GET /api/session reads that re-walked, re-redacted and
re-serialized the ENTIRE transcript (4-15s / 28MB on a 5k-row session,
#7625), and the same shape shipped the full session-level tool_calls list
because windowing was gated on msg_limit being present.
Changes:
- Server: add explicit msg_limit=all escape hatch; window session-level
tool_calls by actual truncation (length comparison) instead of the
presence of a limit parameter. The bare no-limit shape keeps its
full-transcript contract (pinned by
test_state_db_reconciliation_preserves_sidecar_order_when_timestamps_collide
and the run-journal snapshot test).
- Client: refreshSession / _restoreSettledSession / cancel sync /
/compress preflight / /retry / /undo now request the bounded tail
(msg_limit=30) and restore the _messages_truncated/_messages_offset paging
gate instead of hardcoding 'not truncated'; jump-to-start
(_ensureAllMessagesLoaded) and outline jump opt in to the full transcript
via msg_limit=all.
Tests: test_session_default_window.py covers helper semantics, per-call-site
bounded/all shapes, truncation-signal restoration, and a regression guard
that no bare GET /api/session fetch remains in static/. 454 related tests
pass; reverse-verified (reverting the fixes turns the tests red).