@@ -196,4 +196,180 @@ describe('ContextWorkingBufferImpl', () => {
196196 // It should root to itself
197197 expect ( buffer . getPristineNodes ( 'injected1' ) ) . toEqual ( [ injected ] ) ;
198198 } ) ;
199+
200+ describe ( 'syncPristineHistory' , ( ) => {
201+ it ( 'should append newly discovered pristine nodes to the end of the buffer' , ( ) => {
202+ const p1 = createDummyNode (
203+ 'ep1' ,
204+ NodeType . USER_PROMPT ,
205+ 10 ,
206+ undefined ,
207+ 'p1' ,
208+ ) ;
209+ let buffer = ContextWorkingBufferImpl . initialize ( [ p1 ] ) ;
210+
211+ const p2 = createDummyNode (
212+ 'ep1' ,
213+ NodeType . AGENT_THOUGHT ,
214+ 10 ,
215+ undefined ,
216+ 'p2' ,
217+ ) ;
218+ const p3 = createDummyNode (
219+ 'ep1' ,
220+ NodeType . USER_PROMPT ,
221+ 10 ,
222+ undefined ,
223+ 'p3' ,
224+ ) ;
225+
226+ buffer = buffer . syncPristineHistory ( [ p1 , p2 , p3 ] ) ;
227+
228+ expect ( buffer . nodes . map ( ( n ) => n . id ) ) . toEqual ( [ 'p1' , 'p2' , 'p3' ] ) ;
229+ expect ( buffer . getPristineNodes ( 'p3' ) ) . toEqual ( [ p3 ] ) ;
230+ } ) ;
231+
232+ it ( 'should drop working nodes if their pristine root is dropped from authoritative history' , ( ) => {
233+ const p1 = createDummyNode (
234+ 'ep1' ,
235+ NodeType . USER_PROMPT ,
236+ 10 ,
237+ undefined ,
238+ 'p1' ,
239+ ) ;
240+ const p2 = createDummyNode (
241+ 'ep1' ,
242+ NodeType . AGENT_THOUGHT ,
243+ 10 ,
244+ undefined ,
245+ 'p2' ,
246+ ) ;
247+ let buffer = ContextWorkingBufferImpl . initialize ( [ p1 , p2 ] ) ;
248+
249+ // Mutate p2 into m2
250+ const m2 = createDummyNode (
251+ 'ep1' ,
252+ NodeType . AGENT_THOUGHT ,
253+ 5 ,
254+ undefined ,
255+ 'm2' ,
256+ ) ;
257+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
258+ ( m2 as any ) . replacesId = 'p2' ;
259+ buffer = buffer . applyProcessorResult ( 'Masking' , [ p2 ] , [ m2 ] ) ;
260+
261+ expect ( buffer . nodes . map ( ( n ) => n . id ) ) . toEqual ( [ 'p1' , 'm2' ] ) ;
262+
263+ // Upstream graph drops p2 entirely
264+ buffer = buffer . syncPristineHistory ( [ p1 ] ) ;
265+
266+ // m2 should be gone because its root p2 is gone
267+ expect ( buffer . nodes . map ( ( n ) => n . id ) ) . toEqual ( [ 'p1' ] ) ;
268+ } ) ;
269+
270+ it ( 'should correctly weave summarized and mutated nodes into their chronological spots when new nodes arrive' , ( ) => {
271+ // Step 1: Initial state
272+ const p1 = createDummyNode (
273+ 'ep1' ,
274+ NodeType . USER_PROMPT ,
275+ 10 ,
276+ undefined ,
277+ 'p1' ,
278+ ) ;
279+ const p2 = createDummyNode (
280+ 'ep1' ,
281+ NodeType . AGENT_THOUGHT ,
282+ 10 ,
283+ undefined ,
284+ 'p2' ,
285+ ) ;
286+ const p3 = createDummyNode (
287+ 'ep1' ,
288+ NodeType . USER_PROMPT ,
289+ 10 ,
290+ undefined ,
291+ 'p3' ,
292+ ) ;
293+ let buffer = ContextWorkingBufferImpl . initialize ( [ p1 , p2 , p3 ] ) ;
294+
295+ // Step 2: Mutate p2 into m2
296+ const m2 = createDummyNode (
297+ 'ep1' ,
298+ NodeType . AGENT_THOUGHT ,
299+ 5 ,
300+ undefined ,
301+ 'm2' ,
302+ ) ;
303+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
304+ ( m2 as any ) . replacesId = 'p2' ;
305+ buffer = buffer . applyProcessorResult ( 'Masking' , [ p2 ] , [ m2 ] ) ;
306+
307+ expect ( buffer . nodes . map ( ( n ) => n . id ) ) . toEqual ( [ 'p1' , 'm2' , 'p3' ] ) ;
308+
309+ // Step 3: Upstream adds new nodes (p4, p5)
310+ const p4 = createDummyNode (
311+ 'ep1' ,
312+ NodeType . AGENT_THOUGHT ,
313+ 10 ,
314+ undefined ,
315+ 'p4' ,
316+ ) ;
317+ const p5 = createDummyNode (
318+ 'ep1' ,
319+ NodeType . USER_PROMPT ,
320+ 10 ,
321+ undefined ,
322+ 'p5' ,
323+ ) ;
324+
325+ buffer = buffer . syncPristineHistory ( [ p1 , p2 , p3 , p4 , p5 ] ) ;
326+
327+ // The working buffer should re-order to match the authoritative pristine history (p1, p2, p3, p4, p5)
328+ // but retain the mutated state (m2 instead of p2).
329+ // So expected order: p1, m2, p3, p4, p5
330+ expect ( buffer . nodes . map ( ( n ) => n . id ) ) . toEqual ( [
331+ 'p1' ,
332+ 'm2' ,
333+ 'p3' ,
334+ 'p4' ,
335+ 'p5' ,
336+ ] ) ;
337+ } ) ;
338+ it ( 'should drop a non-pristine node if ANY of its multiple pristine roots are dropped from authoritative history' , ( ) => {
339+ const p1 = createDummyNode (
340+ 'ep1' ,
341+ NodeType . USER_PROMPT ,
342+ 10 ,
343+ undefined ,
344+ 'p1' ,
345+ ) ;
346+ const p2 = createDummyNode (
347+ 'ep1' ,
348+ NodeType . AGENT_THOUGHT ,
349+ 10 ,
350+ undefined ,
351+ 'p2' ,
352+ ) ;
353+ let buffer = ContextWorkingBufferImpl . initialize ( [ p1 , p2 ] ) ;
354+
355+ const s1 = createDummyNode (
356+ 'ep1' ,
357+ NodeType . ROLLING_SUMMARY ,
358+ 5 ,
359+ undefined ,
360+ 's1' ,
361+ ) ;
362+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
363+ ( s1 as any ) . abstractsIds = [ 'p1' , 'p2' ] ;
364+ buffer = buffer . applyProcessorResult ( 'Summarizer' , [ p1 , p2 ] , [ s1 ] ) ;
365+
366+ expect ( buffer . nodes . map ( ( n ) => n . id ) ) . toEqual ( [ 's1' ] ) ;
367+
368+ // Upstream graph drops p1 but keeps p2
369+ buffer = buffer . syncPristineHistory ( [ p2 ] ) ;
370+
371+ // s1 should be gone because one of its roots (p1) is gone
372+ expect ( buffer . nodes . map ( ( n ) => n . id ) ) . toEqual ( [ 'p2' ] ) ;
373+ } ) ;
374+ } ) ;
199375} ) ;
0 commit comments