@@ -1832,7 +1832,7 @@ test("notebook rehydration rebuilds the latest epoch and enables notebook tools"
18321832} ) ;
18331833
18341834
1835- test ( "notebook rehydration respects a preset epoch and avoids duplicate active tools" , async ( ) => {
1835+ test ( "notebook rehydration rebuilds from the latest persisted epoch and avoids duplicate active tools" , async ( ) => {
18361836 const pi = new MockPi ( ) ;
18371837 pi . activeTools = [ "read" , "notebook_read" , "notebook_index" ] ;
18381838 const state = createState ( ) ;
@@ -1847,17 +1847,87 @@ test("notebook rehydration respects a preset epoch and avoids duplicate active t
18471847 getBranch : ( ) => [
18481848 { type : "custom" , customType : "notebook-entry" , data : { epoch : 6 , name : "stale" , content : "old" } } ,
18491849 { type : "custom" , customType : "notebook-entry" , data : { epoch : 7 , name : "keep" , content : "fresh" } } ,
1850- { type : "custom" , customType : "notebook-entry" , data : { epoch : 8 , name : "future" , content : "skip " } } ,
1850+ { type : "custom" , customType : "notebook-entry" , data : { epoch : 8 , name : "future" , content : "latest " } } ,
18511851 ] ,
18521852 } ,
18531853 } ,
18541854 ) ;
18551855
1856- assert . equal ( state . epoch , 7 ) ;
1857- assert . deepEqual ( Array . from ( state . notebookPages . entries ( ) ) , [ [ "keep " , "fresh " ] ] ) ;
1856+ assert . equal ( state . epoch , 8 ) ;
1857+ assert . deepEqual ( Array . from ( state . notebookPages . entries ( ) ) , [ [ "future " , "latest " ] ] ) ;
18581858 assert . deepEqual ( pi . activeTools , [ "read" , "notebook_read" , "notebook_index" ] ) ;
18591859} ) ;
18601860
1861+
1862+ test ( "notebook rehydration clears stale in-memory notebook state when persisted history is empty" , async ( ) => {
1863+ const pi = new MockPi ( ) ;
1864+ const state = createState ( ) ;
1865+ state . epoch = 7 ;
1866+ state . notebookPages . set ( "stale" , "stale body" ) ;
1867+ registerNotebookRehydration ( pi as any , state ) ;
1868+ const [ handler ] = pi . handlers . get ( "session_start" ) ! ;
1869+
1870+ await handler (
1871+ { } ,
1872+ {
1873+ sessionManager : {
1874+ getBranch : ( ) => [ ] ,
1875+ } ,
1876+ } ,
1877+ ) ;
1878+
1879+ assert . equal ( state . epoch , 0 ) ;
1880+ assert . deepEqual ( Array . from ( state . notebookPages . entries ( ) ) , [ ] ) ;
1881+ assert . deepEqual ( pi . activeTools , [ "notebook_read" , "notebook_index" ] ) ;
1882+ } ) ;
1883+
1884+
1885+ test ( "session_start rehydrates the latest persisted notebook state through the full hook chain" , async ( ) => {
1886+ resetNotebookWriteLock ( ) ;
1887+ const pi = new MockPi ( ) ;
1888+ pi . activeTools = [ "read" , "notebook_read" ] ;
1889+ registerAgenticoding ( pi as any ) ;
1890+
1891+ try {
1892+ const notebookWrite = pi . tools . get ( "notebook_write" ) ;
1893+ await notebookWrite . execute (
1894+ "seed" ,
1895+ { name : "stale-page" , content : "stale body" } ,
1896+ undefined ,
1897+ undefined ,
1898+ makeTUICtx ( { hasUI : false } ) ,
1899+ ) ;
1900+
1901+ const sessionStartHandlers = pi . handlers . get ( "session_start" ) ! ;
1902+ const ctx = {
1903+ hasUI : false ,
1904+ getContextUsage : ( ) => null ,
1905+ sessionManager : {
1906+ getBranch : ( ) => [
1907+ { type : "custom" , customType : "notebook-entry" , data : { epoch : 6 , name : "stale" , content : "old" } } ,
1908+ { type : "custom" , customType : "notebook-entry" , data : { epoch : 8 , name : "keep" , content : "fresh" } } ,
1909+ { type : "custom" , customType : "notebook-entry" , data : { epoch : 8 , name : "keep" , content : "newer" } } ,
1910+ ] ,
1911+ } ,
1912+ } ;
1913+ for ( const sessionStart of sessionStartHandlers ) {
1914+ await sessionStart ( { reason : "resume" } , ctx as any ) ;
1915+ }
1916+
1917+ const notebookIndex = pi . tools . get ( "notebook_index" ) ;
1918+ const notebookRead = pi . tools . get ( "notebook_read" ) ;
1919+ const indexResult = await notebookIndex . execute ( "1" , { } , undefined , undefined , { } as any ) ;
1920+ assert . deepEqual ( indexResult . details . entries , [ "keep" ] ) ;
1921+
1922+ const readResult = await notebookRead . execute ( "2" , { name : "keep" } , undefined , undefined , { } as any ) ;
1923+ assert . equal ( readResult . details . found , true ) ;
1924+ assert . equal ( readResult . details . body , "newer" ) ;
1925+ assert . deepEqual ( pi . activeTools , [ "read" , "notebook_read" , "notebook_index" ] ) ;
1926+ } finally {
1927+ resetNotebookWriteLock ( ) ;
1928+ }
1929+ } ) ;
1930+
18611931test ( "notebook tools add/get/list return stable contract details" , async ( ) => {
18621932 const pi = new MockPi ( ) ;
18631933 const state = createState ( ) ;
@@ -2189,6 +2259,34 @@ test("saveNotebookPage truncates oversized content before persisting", async ()
21892259 resetNotebookWriteLock ( ) ;
21902260} ) ;
21912261
2262+
2263+ test ( "resetState clears epoch and the next notebook write starts a fresh generation" , async ( ) => {
2264+ resetNotebookWriteLock ( ) ;
2265+ const pi = new MockPi ( ) ;
2266+ const state = createState ( ) ;
2267+ const originalNow = Date . now ;
2268+
2269+ try {
2270+ Date . now = ( ) => 1000 ;
2271+ await saveNotebookPage ( pi as any , state , "entry-a" , "first" ) ;
2272+ await saveNotebookPage ( pi as any , state , "entry-b" , "second" ) ;
2273+ assert . equal ( state . epoch , 1000 ) ;
2274+ assert . equal ( pi . appendedEntries [ 0 ] . data . epoch , 1000 ) ;
2275+ assert . equal ( pi . appendedEntries [ 1 ] . data . epoch , 1000 ) ;
2276+
2277+ resetState ( state ) ;
2278+ assert . equal ( state . epoch , 0 ) ;
2279+
2280+ Date . now = ( ) => 2000 ;
2281+ await saveNotebookPage ( pi as any , state , "entry-c" , "third" ) ;
2282+ assert . equal ( state . epoch , 2000 ) ;
2283+ assert . equal ( pi . appendedEntries [ 2 ] . data . epoch , 2000 ) ;
2284+ } finally {
2285+ Date . now = originalNow ;
2286+ resetNotebookWriteLock ( ) ;
2287+ }
2288+ } ) ;
2289+
21922290test ( "nested spawn invalidate rebuilds from the attached session transcript" , ( ) => {
21932291 const state = createState ( ) ;
21942292 const childSpawnTool = createChildSpawnTool ( state ) ;
0 commit comments