Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 35 additions & 5 deletions static/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -4472,9 +4472,11 @@ function _compressionAnchorIndex(visWithIdx, anchorKey, fallbackIdx=null){
for(let i=visWithIdx.length-1;i>=0;i--){
const candidate=_compressionMessageAnchorKey(visWithIdx[i].m);
if(!candidate) continue;
const anchorTs=String(anchorKey.ts??'');
const candidateTs=String(candidate.ts??'');
if(
candidate.role===String(anchorKey.role||'') &&
String(candidate.ts??'')===String(anchorKey.ts??'') &&
(!anchorTs||!candidateTs||candidateTs===anchorTs) &&
String(candidate.text||'')===String(anchorKey.text||'') &&
Number(candidate.attachments||0)===Number(anchorKey.attachments||0)
){
Expand All @@ -4484,6 +4486,24 @@ function _compressionAnchorIndex(visWithIdx, anchorKey, fallbackIdx=null){
}
return typeof fallbackIdx==='number' ? fallbackIdx : null;
}
function _latestCompressionReferenceMessage(messages, summaryText=''){
if(!Array.isArray(messages)||!messages.length) return {message:null, rawIdx:-1};
const summaryNorm=String(summaryText||'').replace(/\s+/g,' ').trim();
for(let i=messages.length-1;i>=0;i--){
const m=messages[i];
if(!_isContextCompactionMessage(m)) continue;
if(!summaryNorm) return {message:m, rawIdx:i};
let content='';
try{
content=String(msgContent(m)||'');
}catch(_){
content=String((m&&m.content)||'');
}
const contentNorm=content.replace(/\s+/g,' ').trim();
if(contentNorm.includes(summaryNorm)) return {message:m, rawIdx:i};
}
return {message:null, rawIdx:-1};
}
function _compressionReferenceCardHtml(text, open=false){
const preview=text.split(/\n+/).filter(Boolean).slice(0,2).join(' ');
return `
Expand Down Expand Up @@ -4887,7 +4907,10 @@ function renderMessages(options){
$('emptyState').style.display=(vis.length||preservedCompressionTaskMessages.length)?'none':'';
inner.innerHTML='';
const compressionNode=compressionState?_compressionCardsNode(compressionState):null;
const referenceMessage=S.messages.find(m=>_isContextCompactionMessage(m));
const {message:referenceMessage, rawIdx:referenceMessageRawIdx}=_latestCompressionReferenceMessage(
S.messages,
sessionCompressionSummary
);
const referenceText=referenceMessage
? msgContent(referenceMessage)||String(referenceMessage.content||'')
: sessionCompressionSummary;
Expand Down Expand Up @@ -4938,13 +4961,19 @@ function renderMessages(options){
break;
}
}
const insertionAnchor=_compressionAnchorIndex(
renderVisWithIdx,
const insertionAnchorFull=_compressionAnchorIndex(
visWithIdx,
compressionState ? compressionState.anchorMessageKey : sessionCompressionAnchorKey,
compressionState
? (typeof compressionState.anchorVisibleIdx==='number' ? compressionState.anchorVisibleIdx : compressionState.anchorRawIdx)
: sessionCompressionAnchor
);
let insertionAnchor=null;
if(typeof insertionAnchorFull==='number'){
if(insertionAnchorFull<windowStart) insertionAnchor=renderVisWithIdx.length?0:null;
else if(insertionAnchorFull<windowStart+renderVisWithIdx.length) insertionAnchor=insertionAnchorFull-windowStart;
else insertionAnchor=renderVisWithIdx.length?renderVisWithIdx.length-1:null;
}
let _prevSepKey=null;
let currentAssistantTurn=null;
const assistantSegments=new Map();
Expand Down Expand Up @@ -5163,7 +5192,8 @@ function renderMessages(options){
const handoffSummaryStates=_collectHandoffSummaryStates(S.messages);

_insertCompressionLikeNode(compressionNode);
_insertCompressionLikeNode(referenceNode);
if(referenceNode&&referenceMessageRawIdx>=0) _insertCompressionLikeNodeByRawIdx(referenceNode, referenceMessageRawIdx);
else _insertCompressionLikeNode(referenceNode);
_insertCompressionLikeNode(preservedOnlyNode, preservedOnlyAnchor);
_insertCompressionLikeNode(handoffState?_handoffCardsNode(handoffState):null, renderVisWithIdx.length?renderVisWithIdx.length-1:null);
for(const entry of handoffSummaryStates){
Expand Down
60 changes: 60 additions & 0 deletions tests/test_auto_compression_card.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,66 @@ def test_context_anchor_reference_uses_session_summary_fallback():
assert "!!referenceText && (sessionCompressionAnchor!==null || sessionCompressionAnchorKey || sessionCompressionSummary)" in src


def test_compression_anchor_matching_tolerates_legacy_missing_timestamp():
src = _read("static/ui.js")
start = src.find("function _compressionAnchorIndex")
assert start != -1, "compression anchor matcher not found"
end = src.find("function _compressionReferenceCardHtml", start)
assert end != -1, "compression reference renderer not found after anchor matcher"
helper = src[start:end]

assert "const anchorTs=String(anchorKey.ts??'');" in helper
assert "const candidateTs=String(candidate.ts??'');" in helper
assert "(!anchorTs||!candidateTs||candidateTs===anchorTs)" in helper


def test_compression_anchor_index_is_translated_into_render_window():
src = _read("static/ui.js")
start = src.find("const insertionAnchorFull=_compressionAnchorIndex")
assert start != -1, "full compression anchor lookup not found"
end = src.find("let _prevSepKey=null", start)
assert end != -1, "message render loop marker not found after anchor lookup"
block = src[start:end]

assert "_compressionAnchorIndex(\n visWithIdx," in block
assert "insertionAnchorFull<windowStart" in block
assert "insertionAnchorFull-windowStart" in block
assert "windowStart+renderVisWithIdx.length" in block


def test_reference_message_uses_raw_transcript_position_before_anchor_fallback():
src = _read("static/ui.js")

assert "const {message:referenceMessage, rawIdx:referenceMessageRawIdx}=_latestCompressionReferenceMessage(" in src
assert "if(referenceNode&&referenceMessageRawIdx>=0) _insertCompressionLikeNodeByRawIdx(referenceNode, referenceMessageRawIdx);" in src
assert "else _insertCompressionLikeNode(referenceNode);" in src


def test_reference_message_selection_prefers_latest_matching_marker():
src = _read("static/ui.js")
start = src.find("function _latestCompressionReferenceMessage")
assert start != -1, "compression reference selection helper not found"
end = src.find("function _compressionReferenceCardHtml", start)
assert end != -1, "compression reference renderer not found after selection helper"
helper = src[start:end]

assert "for(let i=messages.length-1;i>=0;i--)" in helper
assert "if(!summaryNorm) return {message:m, rawIdx:i};" in helper
assert "if(contentNorm.includes(summaryNorm)) return {message:m, rawIdx:i};" in helper


def test_reference_message_falls_back_to_current_summary_when_only_stale_markers_exist():
src = _read("static/ui.js")
start = src.find("function _latestCompressionReferenceMessage")
assert start != -1, "compression reference selection helper not found"
end = src.find("function _compressionReferenceCardHtml", start)
assert end != -1, "compression reference renderer not found after selection helper"
helper = src[start:end]

assert "const summaryNorm=String(summaryText||'').replace(/\\s+/g,' ').trim();" in helper
assert "return {message:null, rawIdx:-1};" in helper


def test_preserved_task_list_attaches_once_per_render():
src = _read("static/ui.js")

Expand Down
Loading