Files
hermes-webui/tests/test_session_message_window_renderable_tail.py
nesquena-hermes 9d94298278
Some checks failed
Release & Docker / release (push) Has been cancelled
Release v0.51.321 — Release KK (Phase 3 light: load renderable transcript tails, #3790) (#3798)
Phase-3-light. #3790 (@ai-ag2026): expand cold-load transcript window to ~msg_limit renderable rows so tool-heavy sessions don't open showing 1-2 messages. Codex CORE fix: explicit expand_renderable flag (cold-load only; Load-earlier keeps raw cap). Also fixed a recurring CI timing flake (git-parallel test → deterministic Barrier). Full suite 8228, Codex SAFE, Opus SHIP, CI 11/11. Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
2026-06-07 15:22:44 -07:00

148 lines
5.2 KiB
Python

from api.routes import _message_window_for_display
def test_initial_msg_limit_skips_trailing_tool_only_rows():
messages = [
{"role": "user", "content": "question"},
{"role": "assistant", "content": "answer"},
] + [
{"role": "tool", "content": f"tool result {idx}"}
for idx in range(40)
]
window, offset = _message_window_for_display(messages, msg_limit=5)
assert [m["role"] for m in window] == ["user", "assistant"]
assert offset == 0
def test_initial_msg_limit_skips_trailing_empty_partial_activity_rows():
messages = [
{"role": "user", "content": "today question", "timestamp": 200},
{"role": "assistant", "content": "today answer", "timestamp": 201},
] + [
{
"role": "assistant",
"content": "",
"_partial": True,
"timestamp": 100,
"reasoning": f"old cancelled thinking {idx}",
"_partial_tool_calls": [{"name": "terminal", "done": True}],
}
for idx in range(40)
]
window, offset = _message_window_for_display(messages, msg_limit=5)
assert [m["content"] for m in window] == ["today question", "today answer"]
assert offset == 0
def test_msg_limit_keeps_raw_tail_when_it_has_renderable_rows():
messages = [
{"role": "user", "content": f"u{idx}"} if idx % 2 == 0 else {"role": "assistant", "content": f"a{idx}"}
for idx in range(10)
]
window, offset = _message_window_for_display(messages, msg_limit=4)
assert [m["content"] for m in window] == ["u6", "a7", "u8", "a9"]
assert offset == 6
def test_msg_before_anchors_page_before_trailing_tool_rows():
messages = [
{"role": "user", "content": "older"},
{"role": "assistant", "content": "visible before tools"},
] + [
{"role": "tool", "content": f"hidden {idx}"}
for idx in range(12)
] + [
{"role": "assistant", "content": "newer visible"},
]
window, offset = _message_window_for_display(messages, msg_limit=3, msg_before=14)
assert [m["role"] for m in window] == ["user", "assistant"]
assert [m["content"] for m in window] == ["older", "visible before tools"]
assert offset == 0
def test_all_tool_session_keeps_tail_fallback():
messages = [
{"role": "tool", "content": f"tool {idx}"}
for idx in range(6)
]
window, offset = _message_window_for_display(messages, msg_limit=3)
assert [m["content"] for m in window] == ["tool 3", "tool 4", "tool 5"]
assert offset == 3
def test_cold_load_flag_expands_window_to_fill_renderable_rows():
"""With expand_renderable=True, a tail with <limit renderables expands back.
Tail window has 1 renderable (a9) + 4 tool rows. The existing blank-window
fallback does NOT fire (there IS a renderable), so this exercises the NEW
expansion path: it walks back to include 4 more renderable rows.
"""
messages = [
({"role": "user", "content": f"u{i}"} if i % 2 == 0 else {"role": "assistant", "content": f"a{i}"})
for i in range(10)
] + [
{"role": "tool", "content": f"tool {idx}"}
for idx in range(10, 14)
]
window, offset = _message_window_for_display(messages, msg_limit=5, expand_renderable=True)
# Expanded back to index 5 so the window holds 5 renderable rows (a5..a9).
assert offset == 5
assert [m["content"] for m in window if m["role"] != "tool"] == ["a5", "u6", "a7", "u8", "a9"]
def test_cumulative_load_earlier_does_not_expand_without_flag():
"""The 'Load earlier' path (larger msg_limit, no flag, no msg_before) keeps the raw cap.
Codex CORE finding: _loadOlderMessages re-requests with a larger msg_limit
and NO msg_before, so a msg_before-based gate would still expand and pull the
whole tool-heavy transcript. With the explicit expand_renderable flag OFF
(which is how _loadOlderMessages calls it), the raw tail cap is preserved.
"""
messages = [
({"role": "user", "content": f"u{i}"} if i % 2 == 0 else {"role": "assistant", "content": f"a{i}"})
for i in range(10)
] + [
{"role": "tool", "content": f"tool {idx}"}
for idx in range(10, 14)
]
# Same input as the cold-load test, but no expand flag (cumulative path).
window, offset = _message_window_for_display(messages, msg_limit=5, expand_renderable=False)
# Raw tail cap honored: window is the last 5 raw rows (a9 + 4 tools), NOT expanded.
assert offset == 9
assert [m["content"] for m in window] == ["a9", "tool 10", "tool 11", "tool 12", "tool 13"]
def test_cold_load_expands_but_caps_at_total_renderable():
"""Cold-load expansion stops at the session's total renderable count.
When the whole session has fewer renderable rows than msg_limit, the
backward walk must terminate at index 0 (not loop forever) and return the
full source.
"""
messages = [
{"role": "user", "content": "only-user"},
] + [
{"role": "tool", "content": f"tool {idx}"}
for idx in range(8)
]
window, offset = _message_window_for_display(messages, msg_limit=5, expand_renderable=True)
# Only 1 renderable row in the whole session → expand back to index 0.
assert offset == 0
assert window[0]["content"] == "only-user"