3d10a51afe
Pin gateway re-tagging of idle/preflight lifecycle lines as compacting, and assert the TUI keeps that status until compacted rather than restoring the busy bar after 4s.
135 lines
4.6 KiB
Python
135 lines
4.6 KiB
Python
"""Auto-compaction status re-tagging for TUI/desktop summarizing indicators.
|
|
|
|
Auto-compaction reaches the gateway as a generic ``lifecycle`` status. The
|
|
gateway re-tags in-progress lines as ``kind="compacting"`` so drivers can
|
|
show an explicit summarizing indicator instead of the turn looking hung
|
|
(#97239). The marker-only match missed idle/preflight/retry wording.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
|
|
@pytest.fixture()
|
|
def server():
|
|
# Mocks are scoped to the initial import only (see
|
|
# tests/tui_gateway/test_protocol.py for the rationale).
|
|
with patch.dict(
|
|
"sys.modules",
|
|
{
|
|
"hermes_constants": MagicMock(
|
|
get_hermes_home=MagicMock(return_value="/tmp/hermes_test_compaction")
|
|
),
|
|
"hermes_cli.env_loader": MagicMock(),
|
|
"hermes_cli.banner": MagicMock(),
|
|
"hermes_state": MagicMock(),
|
|
},
|
|
):
|
|
mod = importlib.import_module("tui_gateway.server")
|
|
yield mod
|
|
|
|
|
|
def _capture(server, monkeypatch):
|
|
events: list[dict] = []
|
|
monkeypatch.setattr(
|
|
server, "_emit", lambda event, sid, payload=None: events.append(payload or {})
|
|
)
|
|
return events
|
|
|
|
|
|
def test_compaction_lifecycle_is_retagged(server, monkeypatch):
|
|
from agent.conversation_compression import COMPACTION_STATUS
|
|
|
|
events = _capture(server, monkeypatch)
|
|
server._status_update("sid", "lifecycle", COMPACTION_STATUS)
|
|
|
|
assert events == [{"kind": "compacting", "text": COMPACTION_STATUS}]
|
|
|
|
|
|
def test_idle_compaction_lifecycle_is_retagged(server, monkeypatch):
|
|
from agent.conversation_compression import IDLE_COMPACTION_STATUS_TEMPLATE
|
|
|
|
events = _capture(server, monkeypatch)
|
|
text = IDLE_COMPACTION_STATUS_TEMPLATE.format(idle_seconds=747, tokens=44_579)
|
|
server._status_update("sid", "lifecycle", text)
|
|
|
|
assert events == [{"kind": "compacting", "text": text}]
|
|
|
|
|
|
def test_preflight_compaction_lifecycle_is_retagged(server, monkeypatch):
|
|
from agent.conversation_compression import PREFLIGHT_COMPRESSION_STATUS_TEMPLATE
|
|
|
|
events = _capture(server, monkeypatch)
|
|
text = PREFLIGHT_COMPRESSION_STATUS_TEMPLATE.format(
|
|
tokens=120_000, threshold=100_000
|
|
)
|
|
server._status_update("sid", "lifecycle", text)
|
|
|
|
assert events == [{"kind": "compacting", "text": text}]
|
|
|
|
|
|
def test_compaction_done_is_not_retagged_as_compacting(server, monkeypatch):
|
|
from agent.conversation_compression import COMPACTION_DONE_STATUS
|
|
|
|
events = _capture(server, monkeypatch)
|
|
server._status_update("sid", "lifecycle", COMPACTION_DONE_STATUS)
|
|
assert events[0]["kind"] == "lifecycle"
|
|
|
|
events.clear()
|
|
server._status_update("sid", "compacted", COMPACTION_DONE_STATUS)
|
|
assert events[0]["kind"] == "compacted"
|
|
|
|
|
|
def test_other_lifecycle_status_stays_lifecycle(server, monkeypatch):
|
|
events = _capture(server, monkeypatch)
|
|
server._status_update("sid", "lifecycle", "❌ Rate limited after 5 retries")
|
|
|
|
assert events[0]["kind"] == "lifecycle"
|
|
|
|
|
|
def test_overflow_blocked_warning_stays_lifecycle(server, monkeypatch):
|
|
from agent.conversation_compression import CONTEXT_OVERFLOW_BLOCKED_WARNING_TEMPLATE
|
|
|
|
events = _capture(server, monkeypatch)
|
|
text = CONTEXT_OVERFLOW_BLOCKED_WARNING_TEMPLATE.format(
|
|
tokens=85_000, threshold=72_000, reason="cooldown:30"
|
|
)
|
|
server._status_update("sid", "lifecycle", text)
|
|
|
|
assert events[0]["kind"] == "lifecycle"
|
|
|
|
|
|
def test_manual_compressing_kind_is_preserved(server, monkeypatch):
|
|
events = _capture(server, monkeypatch)
|
|
server._status_update("sid", "compressing", "⠋ compressing 40 messages…")
|
|
|
|
assert events[0]["kind"] == "compressing"
|
|
|
|
|
|
def test_is_compaction_progress_status_covers_routine_in_progress_lines():
|
|
from agent.conversation_compression import (
|
|
COMPACTION_DONE_STATUS,
|
|
CONTEXT_OVERFLOW_BLOCKED_WARNING_TEMPLATE,
|
|
ROUTINE_COMPRESSION_STATUS_SAMPLES,
|
|
is_compaction_progress_status,
|
|
)
|
|
|
|
for message in ROUTINE_COMPRESSION_STATUS_SAMPLES:
|
|
if message == COMPACTION_DONE_STATUS:
|
|
assert is_compaction_progress_status(message) is False
|
|
else:
|
|
assert is_compaction_progress_status(message) is True, message
|
|
|
|
assert is_compaction_progress_status(None) is False
|
|
assert is_compaction_progress_status("") is False
|
|
assert is_compaction_progress_status("❌ Rate limited after 5 retries") is False
|
|
overflow = CONTEXT_OVERFLOW_BLOCKED_WARNING_TEMPLATE.format(
|
|
tokens=85_000, threshold=72_000, reason="ineffective"
|
|
)
|
|
assert is_compaction_progress_status(overflow) is False
|