From c529ab29c2228fa37ddf1a8e76b0559805e16d59 Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Sun, 10 May 2026 22:28:24 +0530 Subject: [PATCH] fix(codex): preserve current turn context --- CHANGELOG.md | 1 + .../run-attempt.context-engine.test.ts | 45 ++++++++++++++++--- .../codex/src/app-server/run-attempt.ts | 9 ++++ 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc6c3fdd81f..82c6f6f74a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ Docs: https://docs.openclaw.ai ### Fixes - Agents/image: honor explicit `image` tool model overrides even when `agents.defaults.imageModel` is unset, restoring one-off vision calls for configured multimodal providers. Fixes #79341. Thanks @haumanto. +- Codex app-server: preserve prompt-local current-turn context through context-engine prompt projection, so replied-to Telegram messages stay visible to the Codex model input. - Telegram: pass agent-scoped media roots through gateway message actions so workspace-local media from the active agent is not rejected as cross-agent access. Thanks @frankekn. - CLI/gateway: keep `gateway status --deep` plugin-aware so configured plugin manifest warnings, including missing channel config metadata, stay visible during install and update smoke checks. - Feishu: fall back to a top-level group send when normal group quoted replies target a withdrawn or missing message, preventing replies from disappearing silently while preserving native topic safety. Fixes #79349. Thanks @arlen8411. diff --git a/extensions/codex/src/app-server/run-attempt.context-engine.test.ts b/extensions/codex/src/app-server/run-attempt.context-engine.test.ts index 45b2310eb87..3e29696a38b 100644 --- a/extensions/codex/src/app-server/run-attempt.context-engine.test.ts +++ b/extensions/codex/src/app-server/run-attempt.context-engine.test.ts @@ -237,14 +237,18 @@ function expectRequestInputTextContains( harness: ReturnType, expected: string, ): void { + expect(getRequestInputText(harness)).toContain(expected); +} + +function getRequestInputText(harness: ReturnType): string { const params = requireRequestParams(harness, "turn/start"); const input = requireArray(params.input, "turn/start input"); - expect( - input.some((entry) => { + return input + .map((entry) => { const item = requireRecord(entry, "turn/start input entry"); - return item.type === "text" && optionalString(item.text).includes(expected); - }), - ).toBe(true); + return item.type === "text" ? optionalString(item.text) : ""; + }) + .join("\n"); } describe("runCodexAppServerAttempt context-engine lifecycle", () => { @@ -310,6 +314,37 @@ describe("runCodexAppServerAttempt context-engine lifecycle", () => { expect(openSpy).not.toHaveBeenCalled(); }); + it("keeps current-turn context at the front of the Codex context-engine prompt", async () => { + const sessionFile = path.join(tempDir, "session.jsonl"); + const workspaceDir = path.join(tempDir, "workspace"); + SessionManager.open(sessionFile).appendMessage( + assistantMessage("older context", Date.now()) as never, + ); + const contextEngine = createContextEngine(); + const harness = createStartedThreadHarness(); + const params = createParams(sessionFile, workspaceDir); + params.contextEngine = contextEngine; + params.currentTurnContext = { + text: [ + "Conversation context (untrusted, chronological, selected for current message):", + "#6474 Sun 2026-05-10 22:22 GMT+5:30 [reply target] OpenClaw: anchor REPLYCTX this is the old message", + "#6498 Sun 2026-05-10 22:22 GMT+5:30 OpenClaw: filler REPLYCTX 23", + ].join("\n"), + }; + + const run = runCodexAppServerAttempt(params); + await harness.waitForMethod("turn/start"); + + const inputText = getRequestInputText(harness); + expect(inputText).toContain("OpenClaw assembled context for this turn:"); + expect(inputText).toContain("Current user request:\nhello"); + expect(inputText).toContain("[reply target] OpenClaw: anchor REPLYCTX"); + expect(inputText.trim().startsWith("Conversation context (untrusted")).toBe(true); + + await harness.completeTurn(); + await run; + }); + it("calls afterTurn with the mirrored transcript and runs turn maintenance", async () => { const sessionFile = path.join(tempDir, "session.jsonl"); const workspaceDir = path.join(tempDir, "workspace"); diff --git a/extensions/codex/src/app-server/run-attempt.ts b/extensions/codex/src/app-server/run-attempt.ts index e10ada0d2bd..7cb4baccedf 100644 --- a/extensions/codex/src/app-server/run-attempt.ts +++ b/extensions/codex/src/app-server/run-attempt.ts @@ -640,6 +640,7 @@ export async function runCodexAppServerAttempt( promptText = projection.promptText; prePromptMessageCount = projection.prePromptMessageCount; } + promptText = prependCurrentTurnContext(promptText, params.currentTurnContext); const promptBuild = await resolveAgentHarnessBeforePromptBuildResult({ prompt: promptText, developerInstructions, @@ -2492,6 +2493,14 @@ function joinPresentSections(...sections: Array): string { return sections.filter((section): section is string => Boolean(section?.trim())).join("\n\n"); } +function prependCurrentTurnContext( + prompt: string, + context: EmbeddedRunAttemptParams["currentTurnContext"], +): string { + const text = context?.text.trim(); + return text ? [text, prompt].filter(Boolean).join("\n\n") : prompt; +} + function handleApprovalRequest(params: { method: string; params: JsonValue | undefined;