mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-05-25 11:10:18 +00:00
+4
-1
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- **fix(sessions): preserve distinct retried messages when merging transcripts** ([#2027](https://github.com/nesquena/hermes-webui/issues/2027)). Messaging session transcript merges now use `id`/`message_id` when present before falling back to the legacy role/content/timestamp/tool metadata key, so repeated turns with identical visible text are not silently collapsed.
|
||||
|
||||
## [v0.51.40] — 2026-05-11 — Release P (4-PR contributor batch — quota subprocess hardening + env-lock prewarm + cron one-shot warning + Xiaomi env key)
|
||||
|
||||
### Fixed
|
||||
@@ -22,7 +26,6 @@
|
||||
|
||||
- 4 PRs from 3 different authors. `api/providers.py` was touched by #2030 (+110/-7 in quota probe path) and #2034 (+1 in `_PROVIDER_ENV_VAR` map) with disjoint hunks. `CHANGELOG.md` Unreleased section was the only true conflict (#2033 + #2034 both added bullets); resolved by keeping both entries. Stage merge otherwise clean.
|
||||
|
||||
|
||||
## [v0.51.39] — 2026-05-10 — Release O (4-PR contributor batch — Railway docker fix + Stop-button race + provider resolver + live context tracking)
|
||||
|
||||
### Fixed
|
||||
|
||||
+12
-7
@@ -3081,13 +3081,18 @@ def handle_get(handler, parsed) -> bool:
|
||||
str(m.get("role") or ""),
|
||||
str(m.get("content") or ""),
|
||||
)):
|
||||
key = (
|
||||
str(msg.get("role") or ""),
|
||||
str(msg.get("content") or ""),
|
||||
str(msg.get("timestamp") or ""),
|
||||
str(msg.get("tool_call_id") or ""),
|
||||
str(msg.get("tool_name") or msg.get("name") or ""),
|
||||
)
|
||||
message_identity = msg.get("id") or msg.get("message_id")
|
||||
if message_identity:
|
||||
key = ("message_id", str(message_identity))
|
||||
else:
|
||||
key = (
|
||||
"legacy",
|
||||
str(msg.get("role") or ""),
|
||||
str(msg.get("content") or ""),
|
||||
str(msg.get("timestamp") or ""),
|
||||
str(msg.get("tool_call_id") or ""),
|
||||
str(msg.get("tool_name") or msg.get("name") or ""),
|
||||
)
|
||||
if key in seen_message_keys:
|
||||
continue
|
||||
seen_message_keys.add(key)
|
||||
|
||||
@@ -59,3 +59,67 @@ def test_session_endpoint_merges_sidecar_and_lineage_messages_for_cli_sessions(m
|
||||
"tip assistant",
|
||||
"sidecar tail",
|
||||
]
|
||||
|
||||
|
||||
def test_session_endpoint_preserves_distinct_messages_with_different_ids(monkeypatch):
|
||||
class DummySession:
|
||||
def __init__(self):
|
||||
self.messages = [
|
||||
{
|
||||
"id": "sidecar-retry",
|
||||
"role": "user",
|
||||
"content": "retry the same request",
|
||||
"timestamp": 2.0,
|
||||
}
|
||||
]
|
||||
self.tool_calls = []
|
||||
self.active_stream_id = None
|
||||
self.pending_user_message = None
|
||||
self.pending_attachments = []
|
||||
self.pending_started_at = None
|
||||
self.context_length = 0
|
||||
self.threshold_tokens = 0
|
||||
self.last_prompt_tokens = 0
|
||||
self.model = "openai/gpt-5"
|
||||
self.session_id = "tip"
|
||||
|
||||
def compact(self):
|
||||
return {"session_id": "tip", "title": "Tip", "model": "openai/gpt-5"}
|
||||
|
||||
captured = {}
|
||||
|
||||
monkeypatch.setattr(routes, "get_session", lambda sid, metadata_only=False: DummySession())
|
||||
monkeypatch.setattr(routes, "_clear_stale_stream_state", lambda s: None)
|
||||
monkeypatch.setattr(routes, "_lookup_cli_session_metadata", lambda sid: {"session_source": "messaging"})
|
||||
monkeypatch.setattr(routes, "_is_messaging_session_record", lambda s: True)
|
||||
monkeypatch.setattr(
|
||||
routes,
|
||||
"get_cli_session_messages",
|
||||
lambda sid: [
|
||||
{"role": "user", "content": "root user", "timestamp": 1.0},
|
||||
{
|
||||
"id": "cli-retry",
|
||||
"role": "user",
|
||||
"content": "retry the same request",
|
||||
"timestamp": 2.0,
|
||||
},
|
||||
],
|
||||
)
|
||||
monkeypatch.setattr(routes, "_resolve_effective_session_model_for_display", lambda s: getattr(s, "model", None))
|
||||
monkeypatch.setattr(routes, "_resolve_effective_session_model_provider_for_display", lambda s: None)
|
||||
monkeypatch.setattr(routes, "_merge_cli_sidebar_metadata", lambda raw, meta: raw)
|
||||
monkeypatch.setattr(routes, "redact_session_data", lambda raw: raw)
|
||||
monkeypatch.setattr(routes, "j", lambda handler, payload, status=200: captured.setdefault("payload", payload))
|
||||
|
||||
class Handler:
|
||||
pass
|
||||
|
||||
class Parsed:
|
||||
path = "/api/session"
|
||||
query = "session_id=tip"
|
||||
|
||||
routes.handle_get(Handler(), Parsed())
|
||||
|
||||
session = captured["payload"]["session"]
|
||||
retry_messages = [m for m in session["messages"] if m.get("content") == "retry the same request"]
|
||||
assert [m.get("id") for m in retry_messages] == ["cli-retry", "sidecar-retry"]
|
||||
|
||||
Reference in New Issue
Block a user