mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-08 21:52:57 +00:00
fix(codex): clear resumed context thread after terminal overflow
This commit is contained in:
committed by
Peter Steinberger
parent
62abfd3dcb
commit
d739f44cf9
@@ -1174,6 +1174,82 @@ describe("runCodexAppServerAttempt context-engine lifecycle", () => {
|
||||
expect(savedBinding?.contextEngine?.projection?.epoch).toBe("epoch-before");
|
||||
});
|
||||
|
||||
it("clears a resumed context-engine binding when a turn terminally overflows", async () => {
|
||||
const sessionFile = path.join(tempDir, "session.jsonl");
|
||||
const workspaceDir = path.join(tempDir, "workspace");
|
||||
SessionManager.open(sessionFile).appendMessage(
|
||||
assistantMessage("pre-compaction context", Date.now()) as never,
|
||||
);
|
||||
await writeCodexAppServerBinding(sessionFile, {
|
||||
threadId: "thread-old",
|
||||
cwd: workspaceDir,
|
||||
dynamicToolsFingerprint: "[]",
|
||||
contextEngine: {
|
||||
schemaVersion: 1,
|
||||
engineId: "lossless-claw",
|
||||
policyFingerprint:
|
||||
'{"schemaVersion":1,"engineId":"lossless-claw","ownsCompaction":true,"contextTokenBudget":400000,"projectionMaxChars":1000000}',
|
||||
projection: {
|
||||
schemaVersion: 1,
|
||||
mode: "thread_bootstrap",
|
||||
epoch: "epoch-before",
|
||||
},
|
||||
},
|
||||
});
|
||||
const compact = vi.fn<ContextEngine["compact"]>(async () => ({
|
||||
ok: true,
|
||||
compacted: true,
|
||||
result: { summary: "summary", firstKeptEntryId: "entry-1", tokensBefore: 100_000 },
|
||||
}));
|
||||
const assemble = vi.fn(
|
||||
async ({ messages, prompt }: Parameters<ContextEngine["assemble"]>[0]) => ({
|
||||
messages: [...messages, userMessage(prompt ?? "", 11)],
|
||||
estimatedTokens: 42,
|
||||
systemPromptAddition: "context-engine system",
|
||||
contextProjection: { mode: "thread_bootstrap" as const, epoch: "epoch-before" },
|
||||
}),
|
||||
);
|
||||
const contextEngine = createContextEngine({ assemble, compact });
|
||||
const harness = createStartedThreadHarness(async (method) => {
|
||||
if (method === "thread/resume") {
|
||||
return threadStartResult("thread-old");
|
||||
}
|
||||
if (method === "turn/start") {
|
||||
return turnStartResult("turn-old");
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
const params = createParams(sessionFile, workspaceDir);
|
||||
params.contextEngine = contextEngine;
|
||||
params.contextTokenBudget = 400_000;
|
||||
|
||||
const run = runCodexAppServerAttempt(params);
|
||||
await harness.waitForMethod("turn/start");
|
||||
await harness.notify({
|
||||
method: "turn/completed",
|
||||
params: {
|
||||
threadId: "thread-old",
|
||||
turnId: "turn-old",
|
||||
turn: {
|
||||
id: "turn-old",
|
||||
status: "failed",
|
||||
error: { message: "Codex ran out of room in the model's context window" },
|
||||
items: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
const result = await run;
|
||||
|
||||
expect(result.promptError).toBe("Codex ran out of room in the model's context window");
|
||||
expect(compact).not.toHaveBeenCalled();
|
||||
expect(harness.requests.map((request) => request.method)).toEqual([
|
||||
"thread/resume",
|
||||
"turn/start",
|
||||
"thread/unsubscribe",
|
||||
]);
|
||||
expect(await readCodexAppServerBinding(sessionFile)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not pre-compact over-budget rendered context-engine prompts before Codex turn/start", async () => {
|
||||
const sessionFile = path.join(tempDir, "session.jsonl");
|
||||
const workspaceDir = path.join(tempDir, "workspace");
|
||||
|
||||
@@ -1653,7 +1653,7 @@ export async function runCodexAppServerAttempt(
|
||||
} catch (error) {
|
||||
let turnStartError = error;
|
||||
if (
|
||||
shouldRetryContextEngineTurnOnFreshCodexThread({
|
||||
shouldUseFreshCodexThreadAfterContextEngineOverflow({
|
||||
error: turnStartError,
|
||||
contextEngineActive: Boolean(activeContextEngine),
|
||||
thread,
|
||||
@@ -1964,6 +1964,27 @@ export async function runCodexAppServerAttempt(
|
||||
error: finalPromptErrorMessage,
|
||||
});
|
||||
}
|
||||
if (
|
||||
shouldUseFreshCodexThreadAfterContextEngineOverflow({
|
||||
error: finalPromptError,
|
||||
contextEngineActive: Boolean(activeContextEngine),
|
||||
thread,
|
||||
})
|
||||
) {
|
||||
embeddedAgentLog.warn(
|
||||
"codex app-server context-engine turn overflowed after resume; clearing thread binding for recovery",
|
||||
{
|
||||
threadId: thread.threadId,
|
||||
turnId: activeTurnId,
|
||||
error: finalPromptErrorMessage,
|
||||
},
|
||||
);
|
||||
const preClearSessionFile = activeSessionFile;
|
||||
await clearCodexAppServerBinding(preClearSessionFile);
|
||||
if (activeSessionFile !== preClearSessionFile) {
|
||||
await clearCodexAppServerBinding(activeSessionFile);
|
||||
}
|
||||
}
|
||||
const refreshedUsageLimitPromptError = await refreshCodexUsageLimitPromptError({
|
||||
client,
|
||||
message: finalPromptErrorMessage,
|
||||
@@ -2259,7 +2280,7 @@ function isUnscopedCodexNotification(
|
||||
);
|
||||
}
|
||||
|
||||
function shouldRetryContextEngineTurnOnFreshCodexThread(params: {
|
||||
function shouldUseFreshCodexThreadAfterContextEngineOverflow(params: {
|
||||
error: unknown;
|
||||
contextEngineActive: boolean;
|
||||
thread: CodexAppServerThreadLifecycleBinding;
|
||||
|
||||
Reference in New Issue
Block a user