Merge pull request #4602 from nesquena/stage-4594
Release v0.51.551 — manual /compress preserves visible transcript (#4594/#3133)
This commit is contained in:
commit
cf16ad4bb5
5 changed files with 68 additions and 14 deletions
|
|
@ -3,6 +3,12 @@
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [v0.51.551] — 2026-06-21 — Release TJ (manual /compress keeps the visible conversation)
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Manually compressing a session no longer makes earlier messages disappear from the conversation window (#3133).** The manual `/compress` action was overwriting the visible transcript with the reduced model-facing context (and clearing settled tool history), so messages vanished on screen and after reload even though only the model's context should shrink. It now preserves the visible transcript and tool history and prunes only the model-facing context payload — matching how automatic compression already behaves. Thanks @franksong2702.
|
||||
|
||||
## [v0.51.550] — 2026-06-21 — Release TI (Transparent Stream keeps its activity after a turn settles or reloads)
|
||||
|
||||
### Fixed
|
||||
|
|
|
|||
|
|
@ -18397,15 +18397,13 @@ def _handle_session_compress(handler, body):
|
|||
if _sanitize_messages_for_api(s.messages) != original_messages:
|
||||
return bad(handler, "Session was modified during compression; please retry.", 409)
|
||||
|
||||
s.messages = compressed
|
||||
s.context_messages = compressed
|
||||
s.tool_calls = []
|
||||
s.context_messages = copy.deepcopy(compressed)
|
||||
s.active_stream_id = None
|
||||
s.pending_user_message = None
|
||||
s.pending_attachments = []
|
||||
s.pending_started_at = None
|
||||
s.pending_user_source = None
|
||||
visible_after = visible_messages_for_anchor(compressed, auto_compression=False)
|
||||
visible_after = visible_messages_for_anchor(s.messages, auto_compression=False)
|
||||
s.compression_anchor_visible_idx = max(0, len(visible_after) - 1) if visible_after else None
|
||||
s.compression_anchor_message_key = _anchor_message_key(visible_after[-1]) if visible_after else None
|
||||
summary_text = None
|
||||
|
|
|
|||
|
|
@ -1331,7 +1331,13 @@ def test_sessions_response_backfills_imported_messaging_source_metadata(cleanup_
|
|||
sid = 'gw_legacy_import_weixin_001'
|
||||
cleanup_test_sessions.append(sid)
|
||||
try:
|
||||
_insert_gateway_session(conn, session_id=sid, source='weixin', title='Weixin Session')
|
||||
_insert_gateway_session(
|
||||
conn,
|
||||
session_id=sid,
|
||||
source='weixin',
|
||||
title='Weixin Session',
|
||||
session_key='agent:test:weixin:legacy-import-backfill',
|
||||
)
|
||||
s = Session(
|
||||
session_id=sid,
|
||||
title='Legacy Imported Weixin',
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ def test_legacy_duplicate_anchor_helpers_are_removed():
|
|||
|
||||
assert "def _visible_messages_for_anchor" not in routes_src
|
||||
assert "def _visible_messages_for_compression_anchor" not in streaming_src
|
||||
assert "visible_messages_for_anchor(compressed, auto_compression=False)" in routes_src
|
||||
assert "visible_messages_for_anchor(s.messages, auto_compression=False)" in routes_src
|
||||
assert "visible_messages_for_anchor(s.messages, auto_compression=True)" in streaming_src
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ def _install_fake_compression_runtime(monkeypatch, agent_cls):
|
|||
)
|
||||
|
||||
|
||||
def _make_session(messages=None):
|
||||
def _make_session(messages=None, tool_calls=None):
|
||||
SESSION_DIR.mkdir(parents=True, exist_ok=True)
|
||||
messages = messages or [
|
||||
{"role": "user", "content": "one"},
|
||||
|
|
@ -116,6 +116,7 @@ def _make_session(messages=None):
|
|||
workspace="/tmp/hermes-webui-test",
|
||||
model="openai/gpt-5.4-mini",
|
||||
messages=messages,
|
||||
tool_calls=tool_calls or [],
|
||||
)
|
||||
s.save(touch_updated_at=False)
|
||||
return s.session_id
|
||||
|
|
@ -130,7 +131,23 @@ def test_session_compress_requires_session_id(cleanup_test_sessions):
|
|||
|
||||
def test_session_compress_roundtrip(monkeypatch, cleanup_test_sessions):
|
||||
created = cleanup_test_sessions
|
||||
sid = _make_session()
|
||||
original_messages = [
|
||||
{"role": "user", "content": "one"},
|
||||
{"role": "assistant", "content": "two"},
|
||||
{"role": "user", "content": "three"},
|
||||
{"role": "assistant", "content": "four"},
|
||||
]
|
||||
compressed_messages = [original_messages[0], original_messages[-1]]
|
||||
settled_tool_calls = [
|
||||
{
|
||||
"id": "call_1",
|
||||
"name": "terminal",
|
||||
"assistant_msg_idx": 1,
|
||||
"done": True,
|
||||
"result": "schema.sql",
|
||||
}
|
||||
]
|
||||
sid = _make_session(original_messages, tool_calls=settled_tool_calls)
|
||||
created.append(sid)
|
||||
|
||||
_install_fake_compression_runtime(monkeypatch, _FakeAgent)
|
||||
|
|
@ -144,18 +161,27 @@ def test_session_compress_roundtrip(monkeypatch, cleanup_test_sessions):
|
|||
assert payload["focus_topic"] == "database schema"
|
||||
assert payload["summary"]["headline"] == "Compressed: 4 → 2 messages"
|
||||
assert payload["session"]["session_id"] == sid
|
||||
assert payload["session"]["messages"] == [
|
||||
{"role": "user", "content": "one"},
|
||||
{"role": "assistant", "content": "four"},
|
||||
]
|
||||
assert payload["session"]["messages"] == original_messages
|
||||
assert payload["session"]["tool_calls"] == settled_tool_calls
|
||||
assert payload["session"]["message_count"] == len(original_messages)
|
||||
assert payload["session"]["compression_anchor_summary"] is not None
|
||||
assert payload["session"]["compression_anchor_visible_idx"] == 1
|
||||
assert payload["session"]["compression_anchor_visible_idx"] == 3
|
||||
assert isinstance(payload["session"]["compression_anchor_message_key"], dict)
|
||||
assert payload["session"]["compression_anchor_message_key"].get("role") == "assistant"
|
||||
assert payload["session"]["compression_anchor_message_key"].get("text") == "four"
|
||||
loaded = get_session(sid)
|
||||
persisted = Session.load(sid)
|
||||
assert loaded.messages == original_messages
|
||||
assert loaded.context_messages == compressed_messages
|
||||
assert loaded.tool_calls == settled_tool_calls
|
||||
assert persisted.messages == original_messages
|
||||
assert persisted.context_messages == compressed_messages
|
||||
assert persisted.tool_calls == settled_tool_calls
|
||||
assert loaded.compression_anchor_summary == payload["session"]["compression_anchor_summary"]
|
||||
assert loaded.compression_anchor_visible_idx == payload["session"]["compression_anchor_visible_idx"]
|
||||
assert loaded.compression_anchor_message_key == payload["session"]["compression_anchor_message_key"]
|
||||
assert persisted.compression_anchor_visible_idx == 3
|
||||
assert persisted.compression_anchor_message_key == payload["session"]["compression_anchor_message_key"]
|
||||
assert _FakeAgent.last_instance is not None
|
||||
assert _FakeAgent.last_instance.context_compressor.calls[0]["focus_topic"] == "database schema"
|
||||
|
||||
|
|
@ -185,7 +211,16 @@ def test_session_compress_start_is_async_and_reuses_running_job(monkeypatch, cle
|
|||
self.instances.append(self)
|
||||
|
||||
created = cleanup_test_sessions
|
||||
sid = _make_session()
|
||||
settled_tool_calls = [
|
||||
{
|
||||
"id": "call_async_1",
|
||||
"name": "read_file",
|
||||
"assistant_msg_idx": 1,
|
||||
"done": True,
|
||||
"result": "config.yaml",
|
||||
}
|
||||
]
|
||||
sid = _make_session(tool_calls=settled_tool_calls)
|
||||
created.append(sid)
|
||||
_install_fake_compression_runtime(monkeypatch, BlockingAgent)
|
||||
try:
|
||||
|
|
@ -226,6 +261,15 @@ def test_session_compress_start_is_async_and_reuses_running_job(monkeypatch, cle
|
|||
assert done_payload is not None
|
||||
assert done_payload["summary"]["headline"] == "Compressed: 4 → 2 messages"
|
||||
assert done_payload["session"]["messages"] == [
|
||||
{"role": "user", "content": "one"},
|
||||
{"role": "assistant", "content": "two"},
|
||||
{"role": "user", "content": "three"},
|
||||
{"role": "assistant", "content": "four"},
|
||||
]
|
||||
assert done_payload["session"]["tool_calls"] == settled_tool_calls
|
||||
persisted = Session.load(sid)
|
||||
assert persisted.tool_calls == settled_tool_calls
|
||||
assert persisted.context_messages == [
|
||||
{"role": "user", "content": "one"},
|
||||
{"role": "assistant", "content": "four"},
|
||||
]
|
||||
|
|
|
|||
Loading…
Reference in a new issue