@@ -1824,54 +1824,71 @@ describe("runCodexAppServerAttempt context-engine lifecycle", () => {
18241824 await run ;
18251825 } ) ;
18261826
1827- it ( "calls afterTurn with the mirrored transcript and runs turn maintenance" , async ( ) => {
1828- const sessionFile = path . join ( tempDir , "session.jsonl" ) ;
1829- const workspaceDir = path . join ( tempDir , "workspace" ) ;
1830- const afterTurn = vi . fn (
1831- async ( _params : Parameters < NonNullable < ContextEngine [ "afterTurn" ] > > [ 0 ] ) => undefined ,
1832- ) ;
1833- const maintain = vi . fn ( async ( ) => ( { changed : false , bytesFreed : 0 , rewrittenEntries : 0 } ) ) ;
1834- const contextEngine = createContextEngine ( { afterTurn, maintain, bootstrap : undefined } ) ;
1835- const harness = createStartedThreadHarness ( ) ;
1836- const params = createParams ( sessionFile , workspaceDir ) ;
1837- params . contextEngine = contextEngine ;
1838- params . contextTokenBudget = 111 ;
1839- params . requestedModelId = "gpt-5.4-codex-primary" ;
1840- params . fallbackReason = "provider_unavailable" ;
1841- params . degradedReason = "context_overflow" ;
1842-
1843- const run = runCodexAppServerAttempt ( params ) ;
1844- await harness . waitForMethod ( "turn/start" ) ;
1845- await harness . completeTurn ( ) ;
1846- await run ;
1827+ it . each ( [
1828+ {
1829+ name : "commitment-only" ,
1830+ trigger : "heartbeat" ,
1831+ bootstrapContextRunKind : "commitment-only" ,
1832+ } ,
1833+ {
1834+ name : "Gateway-routed heartbeat" ,
1835+ trigger : "user" ,
1836+ bootstrapContextRunKind : "heartbeat" ,
1837+ } ,
1838+ ] as const ) (
1839+ "keeps $name turns heartbeat-classified through afterTurn maintenance" ,
1840+ async ( testCase ) => {
1841+ const sessionFile = path . join ( tempDir , "session.jsonl" ) ;
1842+ const workspaceDir = path . join ( tempDir , "workspace" ) ;
1843+ const afterTurn = vi . fn (
1844+ async ( _params : Parameters < NonNullable < ContextEngine [ "afterTurn" ] > > [ 0 ] ) => undefined ,
1845+ ) ;
1846+ const maintain = vi . fn ( async ( ) => ( { changed : false , bytesFreed : 0 , rewrittenEntries : 0 } ) ) ;
1847+ const contextEngine = createContextEngine ( { afterTurn, maintain, bootstrap : undefined } ) ;
1848+ const harness = createStartedThreadHarness ( ) ;
1849+ const params = createParams ( sessionFile , workspaceDir ) ;
1850+ params . contextEngine = contextEngine ;
1851+ params . trigger = testCase . trigger ;
1852+ params . bootstrapContextRunKind = testCase . bootstrapContextRunKind ;
1853+ params . contextTokenBudget = 111 ;
1854+ params . requestedModelId = "gpt-5.4-codex-primary" ;
1855+ params . fallbackReason = "provider_unavailable" ;
1856+ params . degradedReason = "context_overflow" ;
1857+
1858+ const run = runCodexAppServerAttempt ( params ) ;
1859+ await harness . waitForMethod ( "turn/start" ) ;
1860+ await harness . completeTurn ( ) ;
1861+ await run ;
18471862
1848- expect ( afterTurn ) . toHaveBeenCalledTimes ( 1 ) ;
1849- const afterTurnCall = requireFirstCallArg ( afterTurn , "afterTurn" ) as Parameters <
1850- NonNullable < ContextEngine [ "afterTurn" ] >
1851- > [ 0 ] ;
1852- expect ( afterTurnCall . sessionId ) . toBe ( "session-1" ) ;
1853- expect ( afterTurnCall . sessionKey ) . toBe ( "agent:main:session-1" ) ;
1854- expect ( afterTurnCall . prePromptMessageCount ) . toBe ( 0 ) ;
1855- expect ( afterTurnCall . tokenBudget ) . toBe ( 111 ) ;
1856- expect ( afterTurnCall . runtimeSettings ) . toMatchObject ( {
1857- runtime : { mode : "degraded" } ,
1858- model : {
1859- requested : "gpt-5.4-codex-primary" ,
1860- resolved : "gpt-5.4-codex" ,
1861- } ,
1862- diagnostics : {
1863- fallbackReason : "provider_unavailable" ,
1864- degradedReason : "context_overflow" ,
1865- } ,
1866- } ) ;
1867- expect ( afterTurnCall . messages . some ( ( message ) => message . role === "user" ) ) . toBe ( true ) ;
1868- expect ( afterTurnCall . messages . some ( ( message ) => message . role === "assistant" ) ) . toBe ( true ) ;
1869- expect ( maintain ) . toHaveBeenCalledTimes ( 1 ) ;
1870- const maintainCall = requireFirstCallArg ( maintain , "maintain" ) as Parameters <
1871- NonNullable < ContextEngine [ "maintain" ] >
1872- > [ 0 ] ;
1873- expect ( maintainCall . runtimeSettings ) . toBe ( afterTurnCall . runtimeSettings ) ;
1874- } ) ;
1863+ expect ( afterTurn ) . toHaveBeenCalledTimes ( 1 ) ;
1864+ const afterTurnCall = requireFirstCallArg ( afterTurn , "afterTurn" ) as Parameters <
1865+ NonNullable < ContextEngine [ "afterTurn" ] >
1866+ > [ 0 ] ;
1867+ expect ( afterTurnCall . sessionId ) . toBe ( "session-1" ) ;
1868+ expect ( afterTurnCall . sessionKey ) . toBe ( "agent:main:session-1" ) ;
1869+ expect ( afterTurnCall . prePromptMessageCount ) . toBe ( 0 ) ;
1870+ expect ( afterTurnCall . tokenBudget ) . toBe ( 111 ) ;
1871+ expect ( afterTurnCall . isHeartbeat ) . toBe ( true ) ;
1872+ expect ( afterTurnCall . runtimeSettings ) . toMatchObject ( {
1873+ runtime : { mode : "degraded" } ,
1874+ model : {
1875+ requested : "gpt-5.4-codex-primary" ,
1876+ resolved : "gpt-5.4-codex" ,
1877+ } ,
1878+ diagnostics : {
1879+ fallbackReason : "provider_unavailable" ,
1880+ degradedReason : "context_overflow" ,
1881+ } ,
1882+ } ) ;
1883+ expect ( afterTurnCall . messages . some ( ( message ) => message . role === "user" ) ) . toBe ( true ) ;
1884+ expect ( afterTurnCall . messages . some ( ( message ) => message . role === "assistant" ) ) . toBe ( true ) ;
1885+ expect ( maintain ) . toHaveBeenCalledTimes ( 1 ) ;
1886+ const maintainCall = requireFirstCallArg ( maintain , "maintain" ) as Parameters <
1887+ NonNullable < ContextEngine [ "maintain" ] >
1888+ > [ 0 ] ;
1889+ expect ( maintainCall . runtimeSettings ) . toBe ( afterTurnCall . runtimeSettings ) ;
1890+ } ,
1891+ ) ;
18751892
18761893 it ( "reloads mirrored history after bootstrap mutates the session transcript" , async ( ) => {
18771894 const sessionFile = path . join ( tempDir , "session.jsonl" ) ;
0 commit comments