@@ -1722,12 +1722,16 @@ describe("runCodexAppServerAttempt", () => {
17221722 ] ) ;
17231723 } ) ;
17241724
1725- it ( "does not inject mirrored history when starting Codex without a native thread binding" , async ( ) => {
1725+ it ( "projects bounded continuity when starting Codex without a native thread binding" , async ( ) => {
17261726 const sessionFile = path . join ( tempDir , "session.jsonl" ) ;
17271727 const workspaceDir = path . join ( tempDir , "workspace" ) ;
17281728 const sessionManager = SessionManager . open ( sessionFile ) ;
1729- sessionManager . appendMessage ( userMessage ( "we are fixing the Opik default project" , Date . now ( ) ) ) ;
1730- sessionManager . appendMessage ( assistantMessage ( "Opik default project context" , Date . now ( ) + 1 ) ) ;
1729+ await sessionManager . appendMessage (
1730+ userMessage ( "we are fixing the Opik default project" , Date . now ( ) ) ,
1731+ ) ;
1732+ await sessionManager . appendMessage (
1733+ assistantMessage ( "Opik default project context" , Date . now ( ) + 1 ) ,
1734+ ) ;
17311735 const harness = createStartedThreadHarness ( ) ;
17321736 const params = createParams ( sessionFile , workspaceDir ) ;
17331737 params . prompt = "make the default webpage openclaw" ;
@@ -1743,14 +1747,52 @@ describe("runCodexAppServerAttempt", () => {
17431747 ( turnStart ?. params as { input ?: Array < { text ?: string } > } | undefined ) ?. input ?. [ 0 ] ?. text ??
17441748 "" ;
17451749
1746- expect ( inputText ) . not . toContain ( "OpenClaw assembled context for this turn:" ) ;
1747- expect ( inputText ) . not . toContain ( "we are fixing the Opik default project" ) ;
1748- expect ( inputText ) . not . toContain ( "Opik default project context" ) ;
1749- expect ( inputText ) . not . toContain ( "Current user request:" ) ;
1750+ expect ( inputText ) . toContain ( "OpenClaw assembled context for this turn:" ) ;
1751+ expect ( inputText ) . toContain ( "we are fixing the Opik default project" ) ;
1752+ expect ( inputText ) . toContain ( "Opik default project context" ) ;
1753+ expect ( inputText ) . toContain ( "Current user request:" ) ;
17501754 expect ( inputText ) . toContain ( "make the default webpage openclaw" ) ;
17511755 } ) ;
17521756
1753- it ( "does not inject newer mirrored history when resuming an existing Codex thread binding" , async ( ) => {
1757+ it ( "keeps thread-start developer instructions stable when adding fresh-thread continuity" , async ( ) => {
1758+ let hookCalls = 0 ;
1759+ const beforePromptBuild = vi . fn ( async ( ) => {
1760+ hookCalls += 1 ;
1761+ return {
1762+ systemPrompt : `custom codex system ${ hookCalls } ` ,
1763+ prependContext : `queued context ${ hookCalls } ` ,
1764+ } ;
1765+ } ) ;
1766+ initializeGlobalHookRunner (
1767+ createMockPluginRegistry ( [ { hookName : "before_prompt_build" , handler : beforePromptBuild } ] ) ,
1768+ ) ;
1769+ const sessionFile = path . join ( tempDir , "session.jsonl" ) ;
1770+ const workspaceDir = path . join ( tempDir , "workspace" ) ;
1771+ const sessionManager = SessionManager . open ( sessionFile ) ;
1772+ await sessionManager . appendMessage ( userMessage ( "prior visible context" , Date . now ( ) ) ) ;
1773+ await sessionManager . appendMessage ( assistantMessage ( "prior assistant context" , Date . now ( ) + 1 ) ) ;
1774+ const harness = createStartedThreadHarness ( ) ;
1775+
1776+ const run = runCodexAppServerAttempt ( createParams ( sessionFile , workspaceDir ) ) ;
1777+ await harness . waitForMethod ( "turn/start" ) ;
1778+ await new Promise < void > ( ( resolve ) => setImmediate ( resolve ) ) ;
1779+ await harness . completeTurn ( { threadId : "thread-1" , turnId : "turn-1" } ) ;
1780+ await run ;
1781+
1782+ expect ( beforePromptBuild ) . toHaveBeenCalled ( ) ;
1783+ const threadStart = harness . requests . find ( ( request ) => request . method === "thread/start" ) ;
1784+ const threadStartParams = threadStart ?. params as { developerInstructions ?: string } | undefined ;
1785+ expect ( threadStartParams ?. developerInstructions ) . toContain ( "custom codex system 1" ) ;
1786+ expect ( threadStartParams ?. developerInstructions ) . not . toContain ( "custom codex system 2" ) ;
1787+ const turnStart = harness . requests . find ( ( request ) => request . method === "turn/start" ) ;
1788+ const inputText =
1789+ ( turnStart ?. params as { input ?: Array < { text ?: string } > } | undefined ) ?. input ?. [ 0 ] ?. text ??
1790+ "" ;
1791+ expect ( inputText ) . toContain ( "queued context" ) ;
1792+ expect ( inputText ) . toContain ( "prior visible context" ) ;
1793+ } ) ;
1794+
1795+ it ( "does not replay mirrored history already covered by an existing Codex binding" , async ( ) => {
17541796 const sessionFile = path . join ( tempDir , "session.jsonl" ) ;
17551797 const workspaceDir = path . join ( tempDir , "workspace" ) ;
17561798 await writeExistingBinding ( sessionFile , workspaceDir , { dynamicToolsFingerprint : "[]" } ) ;
@@ -1760,11 +1802,11 @@ describe("runCodexAppServerAttempt", () => {
17601802 throw new Error ( "expected valid Codex binding timestamp" ) ;
17611803 }
17621804 const sessionManager = SessionManager . open ( sessionFile ) ;
1763- sessionManager . appendMessage (
1764- userMessage ( "we were discussing the Sonnet leak screenshots" , bindingUpdatedAt + 1_000 ) ,
1805+ await sessionManager . appendMessage (
1806+ userMessage ( "we were discussing the Sonnet leak screenshots" , bindingUpdatedAt - 2_000 ) ,
17651807 ) ;
1766- sessionManager . appendMessage (
1767- assistantMessage ( "David Ondrej was mentioned in that prior thread" , bindingUpdatedAt + 2_000 ) ,
1808+ await sessionManager . appendMessage (
1809+ assistantMessage ( "David Ondrej was mentioned in that prior thread" , bindingUpdatedAt - 1_000 ) ,
17681810 ) ;
17691811 const harness = createResumeHarness ( ) ;
17701812 const params = createParams ( sessionFile , workspaceDir ) ;
@@ -1789,6 +1831,132 @@ describe("runCodexAppServerAttempt", () => {
17891831 expect ( inputText ) . toContain ( "is the previous message trustworthy?" ) ;
17901832 } ) ;
17911833
1834+ it ( "projects only newer visible history when a resumed Codex binding is stale" , async ( ) => {
1835+ const sessionFile = path . join ( tempDir , "session.jsonl" ) ;
1836+ const workspaceDir = path . join ( tempDir , "workspace" ) ;
1837+ await writeExistingBinding ( sessionFile , workspaceDir , { dynamicToolsFingerprint : "[]" } ) ;
1838+ const binding = await readCodexAppServerBinding ( sessionFile ) ;
1839+ const bindingUpdatedAt = Date . parse ( binding ?. updatedAt ?? "" ) ;
1840+ if ( ! Number . isFinite ( bindingUpdatedAt ) ) {
1841+ throw new Error ( "expected valid Codex binding timestamp" ) ;
1842+ }
1843+ const sessionManager = SessionManager . open ( sessionFile ) ;
1844+ await sessionManager . appendMessage (
1845+ userMessage ( "old native-owned context" , bindingUpdatedAt - 2_000 ) ,
1846+ ) ;
1847+ await sessionManager . appendMessage (
1848+ userMessage ( "we were discussing the Sonnet leak screenshots" , bindingUpdatedAt + 1_000 ) ,
1849+ ) ;
1850+ await sessionManager . appendMessage (
1851+ assistantMessage ( "David Ondrej was mentioned in that prior thread" , bindingUpdatedAt + 2_000 ) ,
1852+ ) ;
1853+ await sessionManager . appendMessage ( {
1854+ ...assistantMessage ( "copilot mirror context also matters" , bindingUpdatedAt + 3_000 ) ,
1855+ __openclaw : { mirrorIdentity : "copilot:assistant-1" } ,
1856+ } ) ;
1857+ const harness = createResumeHarness ( ) ;
1858+ const params = createParams ( sessionFile , workspaceDir ) ;
1859+ params . prompt = "is the previous message trustworthy?" ;
1860+
1861+ const run = runCodexAppServerAttempt ( params ) ;
1862+ await harness . waitForMethod ( "turn/start" ) ;
1863+ await new Promise < void > ( ( resolve ) => setImmediate ( resolve ) ) ;
1864+ await harness . completeTurn ( { threadId : "thread-existing" , turnId : "turn-1" } ) ;
1865+ await run ;
1866+
1867+ expect ( harness . requests . map ( ( request ) => request . method ) ) . toContain ( "thread/resume" ) ;
1868+ const turnStart = harness . requests . find ( ( request ) => request . method === "turn/start" ) ;
1869+ const inputText =
1870+ ( turnStart ?. params as { input ?: Array < { text ?: string } > } | undefined ) ?. input ?. [ 0 ] ?. text ??
1871+ "" ;
1872+
1873+ expect ( inputText ) . toContain ( "OpenClaw assembled context for this turn:" ) ;
1874+ expect ( inputText ) . not . toContain ( "old native-owned context" ) ;
1875+ expect ( inputText ) . toContain ( "we were discussing the Sonnet leak screenshots" ) ;
1876+ expect ( inputText ) . toContain ( "David Ondrej was mentioned in that prior thread" ) ;
1877+ expect ( inputText ) . toContain ( "copilot mirror context also matters" ) ;
1878+ expect ( inputText ) . toContain ( "Current user request:" ) ;
1879+ expect ( inputText ) . toContain ( "is the previous message trustworthy?" ) ;
1880+ } ) ;
1881+
1882+ it ( "does not project Codex mirrored transcript echoes as stale binding continuity" , async ( ) => {
1883+ const sessionFile = path . join ( tempDir , "session.jsonl" ) ;
1884+ const workspaceDir = path . join ( tempDir , "workspace" ) ;
1885+ await writeExistingBinding ( sessionFile , workspaceDir , { dynamicToolsFingerprint : "[]" } ) ;
1886+ const binding = await readCodexAppServerBinding ( sessionFile ) ;
1887+ const bindingUpdatedAt = Date . parse ( binding ?. updatedAt ?? "" ) ;
1888+ if ( ! Number . isFinite ( bindingUpdatedAt ) ) {
1889+ throw new Error ( "expected valid Codex binding timestamp" ) ;
1890+ }
1891+ const sessionManager = SessionManager . open ( sessionFile ) ;
1892+ await sessionManager . appendMessage ( {
1893+ ...userMessage ( "codex mirrored user echo" , bindingUpdatedAt + 1_000 ) ,
1894+ idempotencyKey : "codex-app-server:user-1" ,
1895+ } ) ;
1896+ await sessionManager . appendMessage ( {
1897+ ...assistantMessage ( "codex mirrored assistant echo" , bindingUpdatedAt + 2_000 ) ,
1898+ __openclaw : { mirrorIdentity : "codex-app-server:assistant-1" } ,
1899+ } ) ;
1900+ const harness = createResumeHarness ( ) ;
1901+ const params = createParams ( sessionFile , workspaceDir ) ;
1902+ params . prompt = "continue from the real user message" ;
1903+
1904+ const run = runCodexAppServerAttempt ( params ) ;
1905+ await harness . waitForMethod ( "turn/start" ) ;
1906+ await new Promise < void > ( ( resolve ) => setImmediate ( resolve ) ) ;
1907+ await harness . completeTurn ( { threadId : "thread-existing" , turnId : "turn-1" } ) ;
1908+ await run ;
1909+
1910+ const turnStart = harness . requests . find ( ( request ) => request . method === "turn/start" ) ;
1911+ const inputText =
1912+ ( turnStart ?. params as { input ?: Array < { text ?: string } > } | undefined ) ?. input ?. [ 0 ] ?. text ??
1913+ "" ;
1914+
1915+ expect ( inputText ) . not . toContain ( "OpenClaw assembled context for this turn:" ) ;
1916+ expect ( inputText ) . not . toContain ( "codex mirrored user echo" ) ;
1917+ expect ( inputText ) . not . toContain ( "codex mirrored assistant echo" ) ;
1918+ expect ( inputText ) . toContain ( "continue from the real user message" ) ;
1919+ } ) ;
1920+
1921+ it ( "does not replay messages persisted during an active native Codex turn" , async ( ) => {
1922+ const sessionFile = path . join ( tempDir , "session.jsonl" ) ;
1923+ const workspaceDir = path . join ( tempDir , "workspace" ) ;
1924+ await writeExistingBinding ( sessionFile , workspaceDir , { dynamicToolsFingerprint : "[]" } ) ;
1925+ const originalBindingUpdatedAt = Date . now ( ) - 60_000 ;
1926+ const bindingPath = `${ sessionFile } .codex-app-server.json` ;
1927+ const bindingPayload = JSON . parse ( await fs . readFile ( bindingPath , "utf8" ) ) as Record <
1928+ string ,
1929+ unknown
1930+ > ;
1931+ bindingPayload . updatedAt = new Date ( originalBindingUpdatedAt ) . toISOString ( ) ;
1932+ await fs . writeFile ( bindingPath , `${ JSON . stringify ( bindingPayload , null , 2 ) } \n` ) ;
1933+ const sessionManager = SessionManager . open ( sessionFile ) ;
1934+ const firstHarness = createResumeHarness ( ) ;
1935+ const firstRun = runCodexAppServerAttempt ( createParams ( sessionFile , workspaceDir ) ) ;
1936+ await firstHarness . waitForMethod ( "turn/start" ) ;
1937+ await sessionManager . appendMessage ( userMessage ( "steered into active native turn" , Date . now ( ) ) ) ;
1938+ await firstHarness . completeTurn ( { threadId : "thread-existing" , turnId : "turn-1" } ) ;
1939+ await firstRun ;
1940+ const completedBinding = await readCodexAppServerBinding ( sessionFile ) ;
1941+ expect ( Date . parse ( completedBinding ?. updatedAt ?? "" ) ) . toBeGreaterThan ( originalBindingUpdatedAt ) ;
1942+
1943+ const secondHarness = createResumeHarness ( ) ;
1944+ const secondParams = createParams ( sessionFile , workspaceDir ) ;
1945+ secondParams . prompt = "continue after steering" ;
1946+ const secondRun = runCodexAppServerAttempt ( secondParams ) ;
1947+ await secondHarness . waitForMethod ( "turn/start" ) ;
1948+ await secondHarness . completeTurn ( { threadId : "thread-existing" , turnId : "turn-1" } ) ;
1949+ await secondRun ;
1950+
1951+ const turnStart = secondHarness . requests . find ( ( request ) => request . method === "turn/start" ) ;
1952+ const inputText =
1953+ ( turnStart ?. params as { input ?: Array < { text ?: string } > } | undefined ) ?. input ?. [ 0 ] ?. text ??
1954+ "" ;
1955+ expect ( inputText ) . not . toContain ( "OpenClaw assembled context for this turn:" ) ;
1956+ expect ( inputText ) . not . toContain ( "steered into active native turn" ) ;
1957+ expect ( inputText ) . toContain ( "continue after steering" ) ;
1958+ } ) ;
1959+
17921960 it ( "does not project mirrored messages on consecutive resumes" , async ( ) => {
17931961 const sessionFile = path . join ( tempDir , "session.jsonl" ) ;
17941962 const workspaceDir = path . join ( tempDir , "workspace" ) ;
@@ -1802,10 +1970,10 @@ describe("runCodexAppServerAttempt", () => {
18021970 bindingPayload . updatedAt = new Date ( oldBindingUpdatedAt ) . toISOString ( ) ;
18031971 await fs . writeFile ( bindingPath , `${ JSON . stringify ( bindingPayload , null , 2 ) } \n` ) ;
18041972 const sessionManager = SessionManager . open ( sessionFile ) ;
1805- sessionManager . appendMessage (
1973+ await sessionManager . appendMessage (
18061974 userMessage ( "we were discussing the Sonnet leak screenshots" , oldBindingUpdatedAt + 1_000 ) ,
18071975 ) ;
1808- sessionManager . appendMessage (
1976+ await sessionManager . appendMessage (
18091977 assistantMessage (
18101978 "David Ondrej was mentioned in that prior thread" ,
18111979 oldBindingUpdatedAt + 2_000 ,
@@ -1824,8 +1992,8 @@ describe("runCodexAppServerAttempt", () => {
18241992 const firstInputText =
18251993 ( firstTurnStart ?. params as { input ?: Array < { text ?: string } > } | undefined ) ?. input ?. [ 0 ]
18261994 ?. text ?? "" ;
1827- expect ( firstInputText ) . not . toContain ( "OpenClaw assembled context for this turn:" ) ;
1828- expect ( firstInputText ) . not . toContain ( "we were discussing the Sonnet leak screenshots" ) ;
1995+ expect ( firstInputText ) . toContain ( "OpenClaw assembled context for this turn:" ) ;
1996+ expect ( firstInputText ) . toContain ( "we were discussing the Sonnet leak screenshots" ) ;
18291997 expect ( firstInputText ) . toContain ( "is the previous message trustworthy?" ) ;
18301998
18311999 const secondHarness = createResumeHarness ( ) ;
@@ -3869,6 +4037,72 @@ describe("runCodexAppServerAttempt", () => {
38694037 expect ( savedBinding ?. threadId ) . toBe ( "thread-1" ) ;
38704038 } ) ;
38714039
4040+ it ( "preserves stale-binding continuity when token pressure forces a fresh Codex thread" , async ( ) => {
4041+ const sessionFile = path . join ( tempDir , "session.jsonl" ) ;
4042+ const workspaceDir = path . join ( tempDir , "workspace" ) ;
4043+ const agentDir = path . join ( tempDir , "agent" ) ;
4044+ await writeExistingBinding ( sessionFile , workspaceDir , { dynamicToolsFingerprint : "[]" } ) ;
4045+ const binding = await readCodexAppServerBinding ( sessionFile ) ;
4046+ const bindingUpdatedAt = Date . parse ( binding ?. updatedAt ?? "" ) ;
4047+ if ( ! Number . isFinite ( bindingUpdatedAt ) ) {
4048+ throw new Error ( "expected valid Codex binding timestamp" ) ;
4049+ }
4050+ const sessionManager = SessionManager . open ( sessionFile ) ;
4051+ await sessionManager . appendMessage (
4052+ userMessage ( "post-binding user context" , bindingUpdatedAt + 1_000 ) ,
4053+ ) ;
4054+ await sessionManager . appendMessage (
4055+ assistantMessage ( "post-binding assistant context" , bindingUpdatedAt + 2_000 ) ,
4056+ ) ;
4057+ await fs . writeFile (
4058+ path . join ( path . dirname ( sessionFile ) , "sessions.json" ) ,
4059+ JSON . stringify ( {
4060+ "agent:main:session-1" : {
4061+ sessionFile,
4062+ totalTokens : 12_000 ,
4063+ } ,
4064+ } ) ,
4065+ ) ;
4066+ const rolloutDir = path . join ( agentDir , "codex-home" , "sessions" ) ;
4067+ await fs . mkdir ( rolloutDir , { recursive : true } ) ;
4068+ await fs . writeFile (
4069+ path . join ( rolloutDir , "rollout-thread-existing.jsonl" ) ,
4070+ `${ JSON . stringify ( {
4071+ payload : {
4072+ type : "token_count" ,
4073+ info : {
4074+ last_token_usage : {
4075+ total_tokens : 220_000 ,
4076+ } ,
4077+ model_context_window : 258_400 ,
4078+ } ,
4079+ } ,
4080+ } ) } \n`,
4081+ ) ;
4082+ const { requests, waitForMethod, completeTurn } = createStartedThreadHarness ( ) ;
4083+ const params = createParams ( sessionFile , workspaceDir ) ;
4084+ params . agentDir = agentDir ;
4085+ params . prompt = "large prompt " . repeat ( 12_000 ) ;
4086+
4087+ const run = runCodexAppServerAttempt ( params , {
4088+ pluginConfig : { appServer : { mode : "yolo" } } ,
4089+ } ) ;
4090+ await waitForMethod ( "turn/start" ) ;
4091+ await completeTurn ( { threadId : "thread-1" , turnId : "turn-1" } ) ;
4092+ await run ;
4093+
4094+ expect ( requests . map ( ( entry ) => entry . method ) ) . toContain ( "thread/start" ) ;
4095+ expect ( requests . map ( ( entry ) => entry . method ) ) . not . toContain ( "thread/resume" ) ;
4096+ const turnStart = requests . find ( ( request ) => request . method === "turn/start" ) ;
4097+ const inputText =
4098+ ( turnStart ?. params as { input ?: Array < { text ?: string } > } | undefined ) ?. input ?. [ 0 ] ?. text ??
4099+ "" ;
4100+ expect ( inputText ) . toContain ( "post-binding user context" ) ;
4101+ expect ( inputText ) . toContain ( "post-binding assistant context" ) ;
4102+ const savedBinding = await readCodexAppServerBinding ( sessionFile ) ;
4103+ expect ( savedBinding ?. threadId ) . toBe ( "thread-1" ) ;
4104+ } ) ;
4105+
38724106 it ( "preserves bound auth when rotating a fallback-fuse native rollout" , async ( ) => {
38734107 const sessionFile = path . join ( tempDir , "session.jsonl" ) ;
38744108 const workspaceDir = path . join ( tempDir , "workspace" ) ;
0 commit comments