@@ -125,6 +125,84 @@ describe("prepareCompaction", () => {
125125 } ) ;
126126 } ) ;
127127
128+ it ( "anchors a forced boundary on the assistant tool call, not a trailing tool result" , ( ) => {
129+ const entries : SessionTreeEntry [ ] = [
130+ {
131+ type : "message" ,
132+ id : "user-1" ,
133+ parentId : null ,
134+ timestamp : "2026-06-17T08:45:00.000Z" ,
135+ message : { role : "user" , content : "Read the notes file." , timestamp : 1 } ,
136+ } ,
137+ {
138+ type : "message" ,
139+ id : "assistant-1" ,
140+ parentId : "user-1" ,
141+ timestamp : "2026-06-17T08:45:10.000Z" ,
142+ message : {
143+ role : "assistant" ,
144+ content : [
145+ { type : "toolCall" , id : "call-1" , name : "read_file" , arguments : { path : "notes.md" } } ,
146+ ] ,
147+ api : "openai-responses" ,
148+ provider : "openai" ,
149+ model : "gpt-test" ,
150+ usage : {
151+ input : 625 ,
152+ output : 6 ,
153+ cacheRead : 172_928 ,
154+ cacheWrite : 0 ,
155+ totalTokens : 173_559 ,
156+ cost : { input : 0 , output : 0 , cacheRead : 0 , cacheWrite : 0 , total : 0 } ,
157+ } ,
158+ stopReason : "toolUse" ,
159+ timestamp : 2 ,
160+ } ,
161+ } ,
162+ {
163+ type : "message" ,
164+ id : "tool-1" ,
165+ parentId : "assistant-1" ,
166+ timestamp : "2026-06-17T08:45:11.000Z" ,
167+ message : {
168+ role : "toolResult" ,
169+ toolCallId : "call-1" ,
170+ toolName : "read_file" ,
171+ content : [ { type : "text" , text : "notes body" } ] ,
172+ isError : false ,
173+ timestamp : 3 ,
174+ } ,
175+ } ,
176+ ] ;
177+
178+ const preparation = prepareCompaction ( entries , DEFAULT_COMPACTION_SETTINGS , { force : true } ) ;
179+
180+ // Anchor must be the assistant that owns the tool call, never the trailing
181+ // tool result, or the rebuilt context would replay an orphaned tool result.
182+ expect ( preparation ) . toEqual ( {
183+ ok : true ,
184+ value : expect . objectContaining ( { firstKeptEntryId : "assistant-1" } ) ,
185+ } ) ;
186+
187+ const compactedContext = buildSessionContext ( [
188+ ...entries ,
189+ {
190+ type : "compaction" ,
191+ id : "compaction-1" ,
192+ parentId : "tool-1" ,
193+ timestamp : "2026-06-17T08:45:20.000Z" ,
194+ summary : "Checkpoint of the file read." ,
195+ firstKeptEntryId : "assistant-1" ,
196+ tokensBefore : 173_559 ,
197+ } ,
198+ ] ) ;
199+ expect ( compactedContext . messages . map ( ( message ) => message . role ) ) . toEqual ( [
200+ "compactionSummary" ,
201+ "assistant" ,
202+ "toolResult" ,
203+ ] ) ;
204+ } ) ;
205+
128206 it ( "shows why the old empty-summary compaction replayed the whole transcript" , ( ) => {
129207 const entries : SessionTreeEntry [ ] = [
130208 {
0 commit comments