























@@ -1722,7 +1722,7 @@ describe("runCodexAppServerAttempt", () => {
17221722]);
17231723});
172417241725-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 () => {
17261726const sessionFile = path.join(tempDir, "session.jsonl");
17271727const workspaceDir = path.join(tempDir, "workspace");
17281728const sessionManager = SessionManager.open(sessionFile);
@@ -1743,14 +1743,52 @@ describe("runCodexAppServerAttempt", () => {
17431743(turnStart?.params as { input?: Array<{ text?: string }> } | undefined)?.input?.[0]?.text ??
17441744"";
174517451746-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:");
1746+expect(inputText).toContain("OpenClaw assembled context for this turn:");
1747+expect(inputText).toContain("we are fixing the Opik default project");
1748+expect(inputText).toContain("Opik default project context");
1749+expect(inputText).toContain("Current user request:");
17501750expect(inputText).toContain("make the default webpage openclaw");
17511751});
175217521753-it("does not inject newer mirrored history when resuming an existing Codex thread binding", async () => {
1753+it("keeps thread-start developer instructions stable when adding fresh-thread continuity", async () => {
1754+let hookCalls = 0;
1755+const beforePromptBuild = vi.fn(async () => {
1756+hookCalls += 1;
1757+return {
1758+systemPrompt: `custom codex system ${hookCalls}`,
1759+prependContext: `queued context ${hookCalls}`,
1760+};
1761+});
1762+initializeGlobalHookRunner(
1763+createMockPluginRegistry([{ hookName: "before_prompt_build", handler: beforePromptBuild }]),
1764+);
1765+const sessionFile = path.join(tempDir, "session.jsonl");
1766+const workspaceDir = path.join(tempDir, "workspace");
1767+const sessionManager = SessionManager.open(sessionFile);
1768+sessionManager.appendMessage(userMessage("prior visible context", Date.now()));
1769+sessionManager.appendMessage(assistantMessage("prior assistant context", Date.now() + 1));
1770+const harness = createStartedThreadHarness();
1771+1772+const run = runCodexAppServerAttempt(createParams(sessionFile, workspaceDir));
1773+await harness.waitForMethod("turn/start");
1774+await new Promise<void>((resolve) => setImmediate(resolve));
1775+await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
1776+await run;
1777+1778+expect(beforePromptBuild).toHaveBeenCalled();
1779+const threadStart = harness.requests.find((request) => request.method === "thread/start");
1780+const threadStartParams = threadStart?.params as { developerInstructions?: string } | undefined;
1781+expect(threadStartParams?.developerInstructions).toContain("custom codex system 1");
1782+expect(threadStartParams?.developerInstructions).not.toContain("custom codex system 2");
1783+const turnStart = harness.requests.find((request) => request.method === "turn/start");
1784+const inputText =
1785+(turnStart?.params as { input?: Array<{ text?: string }> } | undefined)?.input?.[0]?.text ??
1786+"";
1787+expect(inputText).toContain("queued context");
1788+expect(inputText).toContain("prior visible context");
1789+});
1790+1791+it("does not replay mirrored history already covered by an existing Codex binding", async () => {
17541792const sessionFile = path.join(tempDir, "session.jsonl");
17551793const workspaceDir = path.join(tempDir, "workspace");
17561794await writeExistingBinding(sessionFile, workspaceDir, { dynamicToolsFingerprint: "[]" });
@@ -1761,10 +1799,10 @@ describe("runCodexAppServerAttempt", () => {
17611799}
17621800const sessionManager = SessionManager.open(sessionFile);
17631801sessionManager.appendMessage(
1764-userMessage("we were discussing the Sonnet leak screenshots", bindingUpdatedAt + 1_000),
1802+userMessage("we were discussing the Sonnet leak screenshots", bindingUpdatedAt - 2_000),
17651803);
17661804sessionManager.appendMessage(
1767-assistantMessage("David Ondrej was mentioned in that prior thread", bindingUpdatedAt + 2_000),
1805+assistantMessage("David Ondrej was mentioned in that prior thread", bindingUpdatedAt - 1_000),
17681806);
17691807const harness = createResumeHarness();
17701808const params = createParams(sessionFile, workspaceDir);
@@ -1789,6 +1827,133 @@ describe("runCodexAppServerAttempt", () => {
17891827expect(inputText).toContain("is the previous message trustworthy?");
17901828});
179118291830+it("projects only newer visible history when a resumed Codex binding is stale", async () => {
1831+const sessionFile = path.join(tempDir, "session.jsonl");
1832+const workspaceDir = path.join(tempDir, "workspace");
1833+await writeExistingBinding(sessionFile, workspaceDir, { dynamicToolsFingerprint: "[]" });
1834+const binding = await readCodexAppServerBinding(sessionFile);
1835+const bindingUpdatedAt = Date.parse(binding?.updatedAt ?? "");
1836+if (!Number.isFinite(bindingUpdatedAt)) {
1837+throw new Error("expected valid Codex binding timestamp");
1838+}
1839+const sessionManager = SessionManager.open(sessionFile);
1840+sessionManager.appendMessage(userMessage("old native-owned context", bindingUpdatedAt - 2_000));
1841+sessionManager.appendMessage(
1842+userMessage("we were discussing the Sonnet leak screenshots", bindingUpdatedAt + 1_000),
1843+);
1844+sessionManager.appendMessage(
1845+assistantMessage("David Ondrej was mentioned in that prior thread", bindingUpdatedAt + 2_000),
1846+);
1847+const copilotMirrorMessage = {
1848+ ...assistantMessage("copilot mirror context also matters", bindingUpdatedAt + 3_000),
1849+__openclaw: { mirrorIdentity: "copilot:assistant-1" },
1850+} as ReturnType<typeof assistantMessage> & { __openclaw: { mirrorIdentity: string } };
1851+sessionManager.appendMessage(copilotMirrorMessage);
1852+const harness = createResumeHarness();
1853+const params = createParams(sessionFile, workspaceDir);
1854+params.prompt = "is the previous message trustworthy?";
1855+1856+const run = runCodexAppServerAttempt(params);
1857+await harness.waitForMethod("turn/start");
1858+await new Promise<void>((resolve) => setImmediate(resolve));
1859+await harness.completeTurn({ threadId: "thread-existing", turnId: "turn-1" });
1860+await run;
1861+1862+expect(harness.requests.map((request) => request.method)).toContain("thread/resume");
1863+const turnStart = harness.requests.find((request) => request.method === "turn/start");
1864+const inputText =
1865+(turnStart?.params as { input?: Array<{ text?: string }> } | undefined)?.input?.[0]?.text ??
1866+"";
1867+1868+expect(inputText).toContain("OpenClaw assembled context for this turn:");
1869+expect(inputText).not.toContain("old native-owned context");
1870+expect(inputText).toContain("we were discussing the Sonnet leak screenshots");
1871+expect(inputText).toContain("David Ondrej was mentioned in that prior thread");
1872+expect(inputText).toContain("copilot mirror context also matters");
1873+expect(inputText).toContain("Current user request:");
1874+expect(inputText).toContain("is the previous message trustworthy?");
1875+});
1876+1877+it("does not project Codex mirrored transcript echoes as stale binding continuity", async () => {
1878+const sessionFile = path.join(tempDir, "session.jsonl");
1879+const workspaceDir = path.join(tempDir, "workspace");
1880+await writeExistingBinding(sessionFile, workspaceDir, { dynamicToolsFingerprint: "[]" });
1881+const binding = await readCodexAppServerBinding(sessionFile);
1882+const bindingUpdatedAt = Date.parse(binding?.updatedAt ?? "");
1883+if (!Number.isFinite(bindingUpdatedAt)) {
1884+throw new Error("expected valid Codex binding timestamp");
1885+}
1886+const sessionManager = SessionManager.open(sessionFile);
1887+const codexMirrorUserMessage = {
1888+ ...userMessage("codex mirrored user echo", bindingUpdatedAt + 1_000),
1889+idempotencyKey: "codex-app-server:user-1",
1890+} as ReturnType<typeof userMessage> & { idempotencyKey: string };
1891+sessionManager.appendMessage(codexMirrorUserMessage);
1892+const codexMirrorAssistantMessage = {
1893+ ...assistantMessage("codex mirrored assistant echo", bindingUpdatedAt + 2_000),
1894+__openclaw: { mirrorIdentity: "codex-app-server:assistant-1" },
1895+} as ReturnType<typeof assistantMessage> & { __openclaw: { mirrorIdentity: string } };
1896+sessionManager.appendMessage(codexMirrorAssistantMessage);
1897+const harness = createResumeHarness();
1898+const params = createParams(sessionFile, workspaceDir);
1899+params.prompt = "continue from the real user message";
1900+1901+const run = runCodexAppServerAttempt(params);
1902+await harness.waitForMethod("turn/start");
1903+await new Promise<void>((resolve) => setImmediate(resolve));
1904+await harness.completeTurn({ threadId: "thread-existing", turnId: "turn-1" });
1905+await run;
1906+1907+const turnStart = harness.requests.find((request) => request.method === "turn/start");
1908+const inputText =
1909+(turnStart?.params as { input?: Array<{ text?: string }> } | undefined)?.input?.[0]?.text ??
1910+"";
1911+1912+expect(inputText).not.toContain("OpenClaw assembled context for this turn:");
1913+expect(inputText).not.toContain("codex mirrored user echo");
1914+expect(inputText).not.toContain("codex mirrored assistant echo");
1915+expect(inputText).toContain("continue from the real user message");
1916+});
1917+1918+it("does not replay messages persisted during an active native Codex turn", async () => {
1919+const sessionFile = path.join(tempDir, "session.jsonl");
1920+const workspaceDir = path.join(tempDir, "workspace");
1921+await writeExistingBinding(sessionFile, workspaceDir, { dynamicToolsFingerprint: "[]" });
1922+const originalBindingUpdatedAt = Date.now() - 60_000;
1923+const bindingPath = `${sessionFile}.codex-app-server.json`;
1924+const bindingPayload = JSON.parse(await fs.readFile(bindingPath, "utf8")) as Record<
1925+string,
1926+unknown
1927+>;
1928+bindingPayload.updatedAt = new Date(originalBindingUpdatedAt).toISOString();
1929+await fs.writeFile(bindingPath, `${JSON.stringify(bindingPayload, null, 2)}\n`);
1930+const sessionManager = SessionManager.open(sessionFile);
1931+const firstHarness = createResumeHarness();
1932+const firstRun = runCodexAppServerAttempt(createParams(sessionFile, workspaceDir));
1933+await firstHarness.waitForMethod("turn/start");
1934+sessionManager.appendMessage(userMessage("steered into active native turn", Date.now()));
1935+await firstHarness.completeTurn({ threadId: "thread-existing", turnId: "turn-1" });
1936+await firstRun;
1937+const completedBinding = await readCodexAppServerBinding(sessionFile);
1938+expect(Date.parse(completedBinding?.updatedAt ?? "")).toBeGreaterThan(originalBindingUpdatedAt);
1939+1940+const secondHarness = createResumeHarness();
1941+const secondParams = createParams(sessionFile, workspaceDir);
1942+secondParams.prompt = "continue after steering";
1943+const secondRun = runCodexAppServerAttempt(secondParams);
1944+await secondHarness.waitForMethod("turn/start");
1945+await secondHarness.completeTurn({ threadId: "thread-existing", turnId: "turn-1" });
1946+await secondRun;
1947+1948+const turnStart = secondHarness.requests.find((request) => request.method === "turn/start");
1949+const inputText =
1950+(turnStart?.params as { input?: Array<{ text?: string }> } | undefined)?.input?.[0]?.text ??
1951+"";
1952+expect(inputText).not.toContain("OpenClaw assembled context for this turn:");
1953+expect(inputText).not.toContain("steered into active native turn");
1954+expect(inputText).toContain("continue after steering");
1955+});
1956+17921957it("does not project mirrored messages on consecutive resumes", async () => {
17931958const sessionFile = path.join(tempDir, "session.jsonl");
17941959const workspaceDir = path.join(tempDir, "workspace");
@@ -1824,8 +1989,8 @@ describe("runCodexAppServerAttempt", () => {
18241989const firstInputText =
18251990(firstTurnStart?.params as { input?: Array<{ text?: string }> } | undefined)?.input?.[0]
18261991?.text ?? "";
1827-expect(firstInputText).not.toContain("OpenClaw assembled context for this turn:");
1828-expect(firstInputText).not.toContain("we were discussing the Sonnet leak screenshots");
1992+expect(firstInputText).toContain("OpenClaw assembled context for this turn:");
1993+expect(firstInputText).toContain("we were discussing the Sonnet leak screenshots");
18291994expect(firstInputText).toContain("is the previous message trustworthy?");
1830199518311996const secondHarness = createResumeHarness();
@@ -3869,6 +4034,72 @@ describe("runCodexAppServerAttempt", () => {
38694034expect(savedBinding?.threadId).toBe("thread-1");
38704035});
387140364037+it("preserves stale-binding continuity when token pressure forces a fresh Codex thread", async () => {
4038+const sessionFile = path.join(tempDir, "session.jsonl");
4039+const workspaceDir = path.join(tempDir, "workspace");
4040+const agentDir = path.join(tempDir, "agent");
4041+await writeExistingBinding(sessionFile, workspaceDir, { dynamicToolsFingerprint: "[]" });
4042+const binding = await readCodexAppServerBinding(sessionFile);
4043+const bindingUpdatedAt = Date.parse(binding?.updatedAt ?? "");
4044+if (!Number.isFinite(bindingUpdatedAt)) {
4045+throw new Error("expected valid Codex binding timestamp");
4046+}
4047+const sessionManager = SessionManager.open(sessionFile);
4048+sessionManager.appendMessage(
4049+userMessage("post-binding user context", bindingUpdatedAt + 1_000),
4050+);
4051+sessionManager.appendMessage(
4052+assistantMessage("post-binding assistant context", bindingUpdatedAt + 2_000),
4053+);
4054+await fs.writeFile(
4055+path.join(path.dirname(sessionFile), "sessions.json"),
4056+JSON.stringify({
4057+"agent:main:session-1": {
4058+ sessionFile,
4059+totalTokens: 12_000,
4060+},
4061+}),
4062+);
4063+const rolloutDir = path.join(agentDir, "codex-home", "sessions");
4064+await fs.mkdir(rolloutDir, { recursive: true });
4065+await fs.writeFile(
4066+path.join(rolloutDir, "rollout-thread-existing.jsonl"),
4067+`${JSON.stringify({
4068+ payload: {
4069+ type: "token_count",
4070+ info: {
4071+ last_token_usage: {
4072+ total_tokens: 220_000,
4073+ },
4074+ model_context_window: 258_400,
4075+ },
4076+ },
4077+ })}\n`,
4078+);
4079+const { requests, waitForMethod, completeTurn } = createStartedThreadHarness();
4080+const params = createParams(sessionFile, workspaceDir);
4081+params.agentDir = agentDir;
4082+params.prompt = "large prompt ".repeat(12_000);
4083+4084+const run = runCodexAppServerAttempt(params, {
4085+pluginConfig: { appServer: { mode: "yolo" } },
4086+});
4087+await waitForMethod("turn/start");
4088+await completeTurn({ threadId: "thread-1", turnId: "turn-1" });
4089+await run;
4090+4091+expect(requests.map((entry) => entry.method)).toContain("thread/start");
4092+expect(requests.map((entry) => entry.method)).not.toContain("thread/resume");
4093+const turnStart = requests.find((request) => request.method === "turn/start");
4094+const inputText =
4095+(turnStart?.params as { input?: Array<{ text?: string }> } | undefined)?.input?.[0]?.text ??
4096+"";
4097+expect(inputText).toContain("post-binding user context");
4098+expect(inputText).toContain("post-binding assistant context");
4099+const savedBinding = await readCodexAppServerBinding(sessionFile);
4100+expect(savedBinding?.threadId).toBe("thread-1");
4101+});
4102+38724103it("preserves bound auth when rotating a fallback-fuse native rollout", async () => {
38734104const sessionFile = path.join(tempDir, "session.jsonl");
38744105const workspaceDir = path.join(tempDir, "workspace");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。