Manual /compress wrote the compressed payload into BOTH s.messages (visible) and s.context_messages (model-facing) + wiped s.tool_calls -> messages vanished from the window. Fix keeps s.messages + tool history durable, deep-copies compressed only into s.context_messages, anchors off visible msgs. Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
This commit is contained in:
1 parent
ccc000a911
commit
4bf9ff7518
4 files changed
+62
-14
No files matched your search
+2
-4
@@ -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
|
||||
|
||||
|
||||
|
||||
+52
-8
@@ -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"},
|
||||
]
|
||||
|
||||
Reference in new issue
Block a user