mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-05-25 19:20:16 +00:00
fix: surface auto-compression handoff
This commit is contained in:
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Surface automatic-compression handoff metadata through the `compressed` SSE event so the active browser stream keeps the completion card even after the backend rotates to a compressed continuation session. The event now carries both the origin session id and continuation id, and the automatic-compression detail line names the compressed session instead of silently dropping the done state.
|
||||
|
||||
## [v0.51.91] — 2026-05-18 — Release BO (stage-384 — 5-PR full sweep batch — reasoning-replay history fix + archive-extract per-session inbox + fallback streaming warnings + sanitized custom-provider env hints + Slice 3c queue/goal adapter routing)
|
||||
|
||||
### Fixed
|
||||
|
||||
+10
-1
@@ -4345,11 +4345,15 @@ def _run_agent_streaming(
|
||||
# reference stays alive even after the dict entry is removed.
|
||||
# Concurrent readers that already looked up the old ID will still
|
||||
# see the same Lock object until they release it.
|
||||
_compression_origin_session_id = session_id
|
||||
_compression_continuation_session_id = None
|
||||
_agent_sid = getattr(agent, 'session_id', None)
|
||||
_compressed = False
|
||||
if _agent_sid and _agent_sid != session_id:
|
||||
old_sid = session_id
|
||||
new_sid = _agent_sid
|
||||
_compression_origin_session_id = old_sid
|
||||
_compression_continuation_session_id = new_sid
|
||||
s.session_id = new_sid
|
||||
# Carry profile identity across the compression boundary.
|
||||
# Without this, s.profile stays None on the continuation
|
||||
@@ -4426,8 +4430,13 @@ def _run_agent_streaming(
|
||||
_compression_summary_from_messages(s.messages)
|
||||
or _compression_summary_from_messages(s.context_messages)
|
||||
)
|
||||
if _compression_continuation_session_id is None:
|
||||
_compression_continuation_session_id = s.session_id
|
||||
put('compressed', {
|
||||
'session_id': s.session_id,
|
||||
'session_id': _compression_origin_session_id,
|
||||
'old_session_id': _compression_origin_session_id,
|
||||
'new_session_id': _compression_continuation_session_id,
|
||||
'continuation_session_id': _compression_continuation_session_id,
|
||||
'message': 'Context auto-compressed to continue the conversation',
|
||||
'usage': _live_usage_snapshot(),
|
||||
})
|
||||
|
||||
+4
-1
@@ -1809,7 +1809,9 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
|
||||
if(!S.session||S.session.session_id!==activeSid) return;
|
||||
let d={};
|
||||
try{ d=JSON.parse(e.data||'{}')||{}; }catch(_){ d={}; }
|
||||
if(d.session_id&&d.session_id!==activeSid) return;
|
||||
const eventSid=d.old_session_id||d.session_id||activeSid;
|
||||
if(eventSid!==activeSid) return;
|
||||
const continuationSid=d.new_session_id||d.continuation_session_id||'';
|
||||
const message=String(d.message||'Context auto-compressed to continue the conversation').trim();
|
||||
if(d.usage&&typeof _syncCtxIndicator==='function'){
|
||||
S.lastUsage={...(S.lastUsage||{}),...d.usage};
|
||||
@@ -1822,6 +1824,7 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
|
||||
automatic:true,
|
||||
message,
|
||||
summary:{headline:message},
|
||||
continuationSessionId:continuationSid,
|
||||
};
|
||||
setCompressionUi(state);
|
||||
const appended=typeof appendLiveCompressionCard==='function'&&appendLiveCompressionCard(state);
|
||||
|
||||
+4
-1
@@ -5015,7 +5015,10 @@ function _autoCompressionDetailText(state){
|
||||
const running=state&&state.phase==='running';
|
||||
const base=_autoCompressionBaseDetail(state);
|
||||
const elapsedLabel=running?_compressionElapsedLabel(state):'';
|
||||
return elapsedLabel?`Elapsed: ${elapsedLabel}`:base;
|
||||
if(running)return elapsedLabel?`Elapsed: ${elapsedLabel}`:base;
|
||||
const continuation=String(state&&state.continuationSessionId||'').trim();
|
||||
const handoff=continuation?`Continued in compressed session: ${continuation}`:'';
|
||||
return [base,handoff].filter(Boolean).join('\n');
|
||||
}
|
||||
function _autoCompressionCardsHtml(state){
|
||||
const running=state&&state.phase==='running';
|
||||
|
||||
@@ -160,6 +160,19 @@ def test_auto_compression_running_detail_avoids_duplicate_message_text():
|
||||
assert "${base}\\nElapsed:" not in helper
|
||||
|
||||
|
||||
def test_auto_compression_done_detail_surfaces_continuation_handoff():
|
||||
src = _read("static/ui.js")
|
||||
start = src.find("function _autoCompressionDetailText")
|
||||
assert start != -1, "auto compression detail helper not found"
|
||||
end = src.find("function _autoCompressionCardsHtml", start)
|
||||
assert end != -1, "auto compression card helper not found after detail helper"
|
||||
helper = src[start:end]
|
||||
|
||||
assert "continuationSessionId" in helper
|
||||
assert "Continued in compressed session" in helper
|
||||
assert "return [base,handoff].filter(Boolean).join('\\n');" in helper
|
||||
|
||||
|
||||
def test_auto_compression_live_card_keeps_elapsed_state_for_timer_refresh():
|
||||
src = _read("static/ui.js")
|
||||
start = src.find("function appendLiveCompressionCard")
|
||||
@@ -208,7 +221,22 @@ def test_auto_compression_sse_keeps_inactive_and_malformed_paths_safe():
|
||||
assert guard in block
|
||||
assert block.index(guard) < block.index("setCompressionUi")
|
||||
assert "try{ d=JSON.parse(e.data||'{}')||{}; }catch(_){ d={}; }" in block
|
||||
assert "if(d.session_id&&d.session_id!==activeSid) return;" in block
|
||||
assert "const eventSid=d.old_session_id||d.session_id||activeSid;" in block
|
||||
assert "if(eventSid!==activeSid) return;" in block
|
||||
|
||||
|
||||
def test_auto_compression_done_accepts_rotated_continuation_session_event():
|
||||
block = _compressed_listener_block()
|
||||
|
||||
# Auto-compression can rotate the backend session id before the 'compressed'
|
||||
# event is emitted. The browser stream still belongs to the pre-compression
|
||||
# activeSid, so the listener must correlate on old_session_id and keep the
|
||||
# continuation id as display metadata instead of dropping the event.
|
||||
assert "const eventSid=d.old_session_id||d.session_id||activeSid;" in block
|
||||
assert "const continuationSid=d.new_session_id||d.continuation_session_id||'';" in block
|
||||
assert "if(eventSid!==activeSid) return;" in block
|
||||
assert block.index("const eventSid=") < block.index("if(eventSid!==activeSid) return;")
|
||||
assert "continuationSessionId:continuationSid" in block
|
||||
|
||||
|
||||
def test_auto_compression_done_sse_refreshes_context_indicator_usage():
|
||||
@@ -228,10 +256,28 @@ def test_auto_compression_done_payload_includes_live_usage_snapshot():
|
||||
assert end != -1, "compressed SSE payload end not found"
|
||||
block = src[start:end]
|
||||
|
||||
assert "'session_id': s.session_id" in block
|
||||
assert "'session_id': _compression_origin_session_id" in block
|
||||
assert "'old_session_id': _compression_origin_session_id" in block
|
||||
assert "'new_session_id': _compression_continuation_session_id" in block
|
||||
assert "'continuation_session_id': _compression_continuation_session_id" in block
|
||||
assert "'usage': _live_usage_snapshot()" in block
|
||||
|
||||
|
||||
def test_auto_compression_rotation_tracks_origin_and_continuation_ids_for_sse():
|
||||
src = _read("api/streaming.py")
|
||||
rotate_start = src.find("# ── Handle context compression side effects ──")
|
||||
assert rotate_start != -1, "compression side-effect block not found"
|
||||
rotate_end = src.find("# Stamp 'timestamp'", rotate_start)
|
||||
assert rotate_end != -1, "compression side-effect block end not found"
|
||||
block = src[rotate_start:rotate_end]
|
||||
|
||||
assert "_compression_origin_session_id = session_id" in block
|
||||
assert "_compression_continuation_session_id = None" in block
|
||||
assert "_compression_origin_session_id = old_sid" in block
|
||||
assert "_compression_continuation_session_id = new_sid" in block
|
||||
assert "'new_session_id': _compression_continuation_session_id" in block
|
||||
|
||||
|
||||
def test_auto_compression_card_reuses_compression_card_renderer():
|
||||
src = _read("static/ui.js")
|
||||
start = src.find("function _autoCompressionCardsHtml")
|
||||
|
||||
Reference in New Issue
Block a user