@@ -80,6 +80,9 @@ def _run_current_turn_scope_probe() -> dict:
8080 helpers = "\n " .join (
8181 [
8282 _function_body (UI_SRC , "function _stripWorkspaceDisplayPrefix" ),
83+ _function_body (UI_SRC , "function msgContent" ),
84+ _function_body (UI_SRC , "function _isContextCompactionText" ),
85+ _function_body (UI_SRC , "function _isContextCompactionMessage" ),
8386 _function_body (SESSIONS_SRC , "function _messageComparableText" ),
8487 _function_body (SESSIONS_SRC , "function _stripAttachedFilesMarker" ),
8588 _function_body (SESSIONS_SRC , "function _stripForcedSkillEnvelope" ),
@@ -128,6 +131,35 @@ def _run_current_turn_scope_probe() -> dict:
128131 [{{role:'user', content:{ json .dumps (prompt )} , _ts:3}}, liveAssistant]
129132);
130133
134+ const compaction = {{
135+ role:'user',
136+ content:'[CONTEXT COMPACTION — REFERENCE ONLY] Earlier turns were compacted.',
137+ _ts:3.5,
138+ }};
139+ const compactionBase = [historical, historicalAnswer, optimisticCurrent, compaction];
140+ const compactionCandidate = {{role:'user', content:{ json .dumps (prompt )} , _ts:3}};
141+ const compactionCurrentTail = _currentTailUserMessage(compactionBase);
142+ const compactionTailDuplicate = _hasCurrentTailUserDuplicate(compactionBase, compactionCandidate);
143+ const compactionMerged = _mergeInflightTailMessages(
144+ compactionBase,
145+ [compactionCandidate, liveAssistant]
146+ );
147+ const insertedAfterCompaction = _mergePendingSessionMessage(pendingSession, compactionMerged);
148+ const compactionPromptCount = compactionMerged.filter(
149+ m=>m&&m.role==='user'&&m._ts===3&&_normalizeUserTranscriptText(m.content)==={ json .dumps (prompt )}
150+ ).length;
151+ const compactionMarkerRetained = compactionMerged.some(m=>_isContextCompactionMessage(m));
152+ const compactionLiveAssistantRetained = compactionMerged.some(
153+ m=>m&&m.role==='assistant'&&m._live&&m.content==='working'
154+ );
155+ const completedBoundaryDedupe = _hasCurrentTailUserDuplicate(
156+ [historical, historicalAnswer, compaction],
157+ {{role:'user', content:{ json .dumps (prompt )} , _ts:3}}
158+ );
159+ const distinctCompletedTurnPromptCount = inflightAfterHistory.filter(
160+ m=>m&&m.role==='user'&&_normalizeUserTranscriptText(m.content)==={ json .dumps (prompt )}
161+ ).length;
162+
131163process.stdout.write(JSON.stringify({{
132164 insertedAfterHistory,
133165 pendingAfterHistoryRoles: pendingAfterHistory.map(m=>m.role),
@@ -137,6 +169,14 @@ def _run_current_turn_scope_probe() -> dict:
137169 pendingWithCurrentRoles: pendingWithCurrent.map(m=>m.role),
138170 inflightAfterHistoryRoles: inflightAfterHistory.map(m=>m.role),
139171 inflightWithCurrentRoles: inflightWithCurrent.map(m=>m.role),
172+ insertedAfterCompaction,
173+ compactionCurrentTailContent: compactionCurrentTail&&compactionCurrentTail.content,
174+ compactionTailDuplicate,
175+ compactionPromptCount,
176+ compactionMarkerRetained,
177+ compactionLiveAssistantRetained,
178+ completedBoundaryDedupe,
179+ distinctCompletedTurnPromptCount,
140180}}));
141181"""
142182 proc = subprocess .run (["node" , "-e" , script ], check = True , capture_output = True , text = True )
@@ -157,6 +197,8 @@ def _run_pending_session_message_probe() -> dict:
157197 _function_body (SESSIONS_SRC , "function _normalizeUserTranscriptText" ),
158198 _function_body (SESSIONS_SRC , "function _sameTranscriptMessage" ),
159199 _function_body (UI_SRC , "function _pendingCurrentTailUserMessage" ),
200+ _function_body (UI_SRC , "function _isContextCompactionText" ),
201+ _function_body (UI_SRC , "function _isContextCompactionMessage" ),
160202 _function_body (UI_SRC , "function getPendingSessionMessage" ),
161203 ]
162204 )
@@ -168,8 +210,24 @@ def _run_pending_session_message_probe() -> dict:
168210const historicalAnswer = {{role:'assistant', content:'done', _ts:2}};
169211const currentTail = {{role:'user', content:prompt, _ts:3}};
170212const currentWorkspaceTail = {{role:'user', content:{ json .dumps (current_workspace_prompt )} , _ts:3}};
213+ const currentTailForCompaction = {{role:'user', content:prompt, _ts:3}};
171214const liveAssistant = {{role:'assistant', content:'working', _live:true, _ts:4}};
172215const attachments = [{{name:'note.txt', path:'note.txt', mime:'text/plain'}}];
216+ const compactionMarker = {{
217+ role:'user',
218+ content:'[CONTEXT COMPACTION — REFERENCE ONLY] Earlier turns were compacted.',
219+ _ts:3.5,
220+ }};
221+ const repeatedPromptTurnOne = {{role:'user', content:prompt, _ts:1}};
222+ const repeatedPromptAnswerOne = {{role:'assistant', content:'done', _ts:2}};
223+ const repeatedPromptTurnTwo = {{role:'user', content:prompt, _ts:3}};
224+ const repeatedPromptAnswerTwo = {{role:'assistant', content:'done', _ts:4}};
225+ const repeatedCompletedBase = [
226+ repeatedPromptTurnOne,
227+ repeatedPromptAnswerOne,
228+ repeatedPromptTurnTwo,
229+ repeatedPromptAnswerTwo,
230+ ];
173231
174232const fromHistoricalSameText = getPendingSessionMessage(
175233 {{pending_user_message:prompt, pending_started_at:3}},
@@ -196,6 +254,21 @@ def _run_pending_session_message_probe() -> dict:
196254 {{pending_user_message:prompt, pending_started_at:4}},
197255 [historical, historicalAnswer, {{role:'user', content:'different prompt', _ts:3}}]
198256);
257+ const compactionTailResult = getPendingSessionMessage(
258+ {{pending_user_message:prompt, pending_started_at:4, pending_attachments:attachments}},
259+ [historical, historicalAnswer, currentTailForCompaction, compactionMarker]
260+ );
261+ const repeatedCompletedResult = getPendingSessionMessage(
262+ {{pending_user_message:prompt, pending_started_at:5}},
263+ repeatedCompletedBase
264+ );
265+ const repeatedCompletedMessages = repeatedCompletedResult
266+ ? [...repeatedCompletedBase, repeatedCompletedResult]
267+ : repeatedCompletedBase;
268+ const repeatedCompletedPromptCount = repeatedCompletedMessages.filter(
269+ m=>m&&m.role==='user'&&_normalizeUserTranscriptText(m.content)===prompt
270+ ).length;
271+ const compactionCurrentTail = _pendingCurrentTailUserMessage([historical, historicalAnswer, currentTailForCompaction, compactionMarker]);
199272
200273process.stdout.write(JSON.stringify({{
201274 historicalSameTextSurvives: !!fromHistoricalSameText && fromHistoricalSameText.content===prompt && fromHistoricalSameText._pending===true,
@@ -205,6 +278,12 @@ def _run_pending_session_message_probe() -> dict:
205278 workspaceCurrentTailDedupe: workspaceCurrentResult===null,
206279 liveAfterCurrentTailDedupe: liveAfterCurrentResult===null,
207280 differentCurrentTailSurvives: !!differentTailResult && differentTailResult.content===prompt && differentTailResult._pending===true,
281+ compactionBoundaryDedupe: compactionTailResult===null,
282+ compactionBoundaryCurrentTail: compactionCurrentTail&&compactionCurrentTail.role==='user'&&compactionCurrentTail.content===prompt,
283+ compactionCurrentTailAttachmentsCopied: Array.isArray(currentTailForCompaction.attachments) && currentTailForCompaction.attachments[0].name==='note.txt',
284+ repeatedCompletedPromptsRemainValid: repeatedCompletedPromptCount===3,
285+ isContextCompactionText: _isContextCompactionText(compactionMarker.content),
286+ isContextCompactionMessage: _isContextCompactionMessage(compactionMarker),
208287}}));
209288"""
210289 proc = subprocess .run (["node" , "-e" , script ], check = True , capture_output = True , text = True )
@@ -385,6 +464,15 @@ def test_user_turn_dedupe_is_scoped_to_current_turn_by_behavior():
385464 assert result ["inflightAfterHistoryRoles" ] == ["user" , "assistant" , "user" , "assistant" ]
386465 assert result ["inflightWithCurrentRoles" ] == ["user" , "assistant" , "user" , "assistant" ]
387466
467+ assert result ["insertedAfterCompaction" ] is False
468+ assert result ["compactionCurrentTailContent" ] == "[Workspace::v1: /tmp/current]\n repeat me"
469+ assert result ["compactionTailDuplicate" ] is True
470+ assert result ["compactionPromptCount" ] == 1
471+ assert result ["compactionMarkerRetained" ] is True
472+ assert result ["compactionLiveAssistantRetained" ] is True
473+ assert result ["completedBoundaryDedupe" ] is False
474+ assert result ["distinctCompletedTurnPromptCount" ] == 2
475+
388476
389477def test_get_pending_session_message_keeps_deferred_repeat_prompt_by_behavior ():
390478 """Deferred active reload must not hide a current repeat prompt.
@@ -404,6 +492,12 @@ def test_get_pending_session_message_keeps_deferred_repeat_prompt_by_behavior():
404492 assert result ["workspaceCurrentTailDedupe" ] is True
405493 assert result ["liveAfterCurrentTailDedupe" ] is True
406494 assert result ["differentCurrentTailSurvives" ] is True
495+ assert result ["compactionBoundaryDedupe" ] is True
496+ assert result ["compactionBoundaryCurrentTail" ] is True
497+ assert result ["compactionCurrentTailAttachmentsCopied" ] is True
498+ assert result ["repeatedCompletedPromptsRemainValid" ] is True
499+ assert result ["isContextCompactionText" ] is True
500+ assert result ["isContextCompactionMessage" ] is True
407501
408502
409503def test_live_tool_matching_uses_the_same_aliases_as_live_card_dedup ():
0 commit comments