fix(#6419): route every recovery path through one pending_user_message merge helper

The mid-stream reconnect path could render the just-sent user message below
the live streaming assistant response it triggered, because
`refreshSession()` appended `pending_user_message` at the end of
`S.messages` unconditionally while `loadSession()` correctly inserted
it before the first live assistant message. When the client went through
its reconnect/recovery path mid-turn (typical on iOS PWA screen
lock/resume) the live assistant tail rendered above the prompt.

Centralize every recovery/reconnect reinsertion through one
identity-aware helper (`_mergePendingSessionMessage`) that:

- inserts the owning user turn before its live assistant/Worklog tail,
- appends only when no live tail exists,
- deduplicates against the current tail user row,
- remains idempotent across repeated resume probes.

`refreshSession()` now calls the same helper instead of pushing at
the end. `loadSession()` continues to use the helper unchanged.

Adds `tests/test_issue6419_pending_user_reconnect_order.py` pinning:
- the helper is defined exactly once at module top level,
- it inserts before live assistant and falls back to append,
- `refreshSession()` uses the helper and does not push unconditionally,
- `loadSession()` still merges before renderMessages in the INFLIGHT
  reattach branch.

Renames the inner end-marker in
`tests/test_gateway_sse_reconnect_dedupe.py` to a stable inside-loadSession
boundary (`// Phase 2a:`) since the helper is no longer nested.
This commit is contained in:
nankingjing authored and nesquena-hermes committed 2026-07-29 22:37:58 +00:00
1 parent ab6f0855a5
commit 95a1647557
5 files changed
+310 -19

No files matched your search

+3 -1
View File
@@ -129,7 +129,9 @@ if(!_isDuplicateGatewaySessionSnapshot([null, {session_id:'web-2', session_sourc
def test_load_session_persists_only_after_metadata_loads():
"""Do not overwrite the last good localStorage sid before /api/session succeeds."""
src = _read(SESSIONS_JS)
load = _block(src, "async function loadSession(sid)", "function _mergePendingSessionMessage")
# _mergePendingSessionMessage was lifted to a top-level helper for #6419,
# so use a stable boundary inside loadSession instead.
load = _block(src, "async function loadSession(sid)", "// Phase 2a:")
api_pos = load.index("data = await api(`/api/session")
persist_pos = load.index("localStorage.setItem('hermes-webui-session',S.session.session_id)")