This commit is contained in:
nesquena-hermes committed 2026-06-12 07:07:33 +00:00
commit 0e7cf6c388
2 files changed
+106

No files matched your search

+42
View File
@@ -3404,6 +3404,47 @@ def _merged_session_messages_for_display(session, cli_messages=None) -> list:
return sidecar_messages
def _merged_webui_lineage_messages_for_display(session, messages=None) -> list:
"""Include immediate parent-only rows when a WebUI continuation sidecar is partial.
Compression/continuation sessions should render as one conversation. Most
child sidecars are cumulative, so this is usually a cheap no-op. If a child
sidecar accidentally omits rows that still exist in the immediate parent,
merge those parent-only rows into the display transcript. Explicit forks and
generic child-session rows remain isolated; they intentionally start from a
subset of their parent.
"""
primary_messages = list(messages if messages is not None else (getattr(session, "messages", []) or []))
parent_id = str(getattr(session, "parent_session_id", "") or "").strip()
if not parent_id:
return primary_messages
source = str(getattr(session, "session_source", "") or "").strip().lower()
relationship = str(getattr(session, "relationship_type", "") or "").strip().lower()
if source == "fork" or relationship == "child_session":
return primary_messages
try:
parent = get_session(parent_id, metadata_only=False)
except Exception:
return primary_messages
parent_messages = list(getattr(parent, "messages", []) or [])
if not parent_messages:
return primary_messages
merged_messages = []
seen_message_keys = set()
for msg in sorted(list(parent_messages) + list(primary_messages), key=lambda m: (
float(m.get("timestamp") or 0),
str(m.get("role") or ""),
str(m.get("content") or ""),
)):
key = _session_message_merge_key(msg)
if key in seen_message_keys:
continue
seen_message_keys.add(key)
merged_messages.append(msg)
return merged_messages
def _message_summary(messages) -> dict:
messages = list(messages or [])
last_message_at = 0.0
@@ -5929,6 +5970,7 @@ def handle_get(handler, parsed) -> bool:
state_db_messages,
truncation_watermark=getattr(s, "truncation_watermark", None),
)
_all_msgs = _merged_webui_lineage_messages_for_display(s, _all_msgs)
else:
if is_messaging_session and cli_messages:
_all_msgs = _merged_session_messages_for_display(s, cli_messages)
+64
View File
@@ -0,0 +1,64 @@
from types import SimpleNamespace
import api.routes as routes
def _msg(role, content, ts):
return {"role": role, "content": content, "timestamp": ts}
def test_webui_lineage_display_merge_includes_parent_only_rows(monkeypatch):
parent = SimpleNamespace(
session_id="parent",
messages=[
_msg("user", "first prompt", 1),
_msg("assistant", "first answer", 2),
_msg("user", "parent only prompt", 3),
_msg("assistant", "parent only answer", 4),
],
)
tip = SimpleNamespace(
session_id="tip",
parent_session_id="parent",
session_source="webui",
messages=[
_msg("user", "first prompt", 1),
_msg("assistant", "first answer", 2),
_msg("user", "tip prompt", 5),
_msg("assistant", "tip answer", 6),
],
truncation_watermark=None,
)
monkeypatch.setattr(routes, "get_session", lambda sid, metadata_only=False: parent)
merged = routes._merged_webui_lineage_messages_for_display(tip, tip.messages)
assert [m["content"] for m in merged] == [
"first prompt",
"first answer",
"parent only prompt",
"parent only answer",
"tip prompt",
"tip answer",
]
def test_webui_lineage_display_merge_skips_explicit_forks(monkeypatch):
parent = SimpleNamespace(
session_id="parent",
messages=[_msg("user", "parent-only", 1)],
)
fork = SimpleNamespace(
session_id="fork",
parent_session_id="parent",
session_source="fork",
messages=[_msg("user", "fork starts here", 2)],
truncation_watermark=None,
)
monkeypatch.setattr(routes, "get_session", lambda sid, metadata_only=False: parent)
merged = routes._merged_webui_lineage_messages_for_display(fork, fork.messages)
assert [m["content"] for m in merged] == ["fork starts here"]