fix: dedupe sidebar completion session ids

This commit is contained in:
ai-ag2026 2026-05-30 18:13:47 +02:00
parent 7dc4273a21
commit 9458df6f4d
4 changed files with 66 additions and 2 deletions

View file

@ -3,6 +3,10 @@
## [Unreleased]
### Fixed
- Avoid duplicate sidebar rows when a compressed session completes after both the preserved snapshot id and continuation id are already present in the session list.
## [v0.51.171] — 2026-05-30 — Release EQ (stage-batch53 — tool-output card badge + Neon opt-in skin)
### Added

View file

@ -361,7 +361,9 @@ function _markSessionCompletedInList(session, previousSid = null) {
if (!session || !Array.isArray(_allSessions)) return;
const finalSid = session.session_id || previousSid;
if (!finalSid) return;
const idx = _allSessions.findIndex(s => s && (s.session_id === finalSid || s.session_id === previousSid));
const finalIdx = _allSessions.findIndex(s => s && s.session_id === finalSid);
const previousIdx = previousSid ? _allSessions.findIndex(s => s && s.session_id === previousSid) : -1;
const idx = finalIdx >= 0 ? finalIdx : previousIdx;
if (idx < 0) return;
const {messages: _messages, tool_calls: _toolCalls, ...sessionMeta} = session;
const messageCount = Number(
@ -384,6 +386,11 @@ function _markSessionCompletedInList(session, previousSid = null) {
_sessionStreamingById.set(finalSid, false);
_forgetObservedStreamingSession(finalSid);
if (previousSid && previousSid !== finalSid) {
for (let i = _allSessions.length - 1; i >= 0; i--) {
if (i !== idx && _allSessions[i] && _allSessions[i].session_id === previousSid) {
_allSessions.splice(i, 1);
}
}
_sessionStreamingById.delete(previousSid);
_forgetObservedStreamingSession(previousSid);
_sessionListSnapshotById.delete(previousSid);

View file

@ -100,7 +100,9 @@ def test_sidebar_cache_completion_handles_compression_session_rotation():
assert "function _markSessionCompletedInList(session, previousSid = null)" in helper_block
assert "const finalSid = session.session_id || previousSid;" in helper_block
assert "s.session_id === finalSid || s.session_id === previousSid" in helper_block
assert "const finalIdx = _allSessions.findIndex(s => s && s.session_id === finalSid);" in helper_block
assert "const previousIdx = previousSid ? _allSessions.findIndex(s => s && s.session_id === previousSid) : -1;" in helper_block
assert "const idx = finalIdx >= 0 ? finalIdx : previousIdx;" in helper_block
assert "const {messages: _messages, tool_calls: _toolCalls, ...sessionMeta} = session;" in helper_block
assert "...sessionMeta" in helper_block
assert "session_id: finalSid" in helper_block

View file

@ -0,0 +1,51 @@
import json
import subprocess
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
def _extract_js_function(src: str, name: str) -> str:
start = src.index(f"function {name}(")
brace = src.index("{", start)
depth = 0
for idx in range(brace, len(src)):
ch = src[idx]
if ch == "{":
depth += 1
elif ch == "}":
depth -= 1
if depth == 0:
return src[start : idx + 1]
raise AssertionError(f"could not extract {name}")
def test_mark_session_completed_in_list_dedupes_old_and_new_sid_rows():
sessions_src = (REPO_ROOT / "static" / "sessions.js").read_text(encoding="utf-8")
fn_src = _extract_js_function(sessions_src, "_markSessionCompletedInList")
script = f"""
let _allSessions = [
{{ session_id: 'old', title: 'Before', message_count: 10, pre_compression_snapshot: true }},
{{ session_id: 'new', title: 'After', message_count: 2, parent_session_id: 'old' }},
];
let _sessionStreamingById = new Map([['old', true], ['new', true]]);
let _sessionListSnapshotById = new Map([['old', {{message_count: 10}}]]);
function _forgetObservedStreamingSession(_sid) {{}}
function renderSessionListFromCache() {{}}
{fn_src}
_markSessionCompletedInList({{
session_id: 'new',
title: 'After done',
message_count: 3,
parent_session_id: 'old',
}}, 'old');
console.log(JSON.stringify({{rows: _allSessions, oldStreaming: _sessionStreamingById.has('old')}}));
"""
result = subprocess.run(["node", "-e", script], check=True, text=True, capture_output=True)
payload = json.loads(result.stdout)
assert [row["session_id"] for row in payload["rows"]] == ["new"]
assert payload["rows"][0]["title"] == "After done"
assert payload["rows"][0]["message_count"] == 3
assert payload["oldStreaming"] is False