mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix(codex): skip stale bootstrap history without engine
This commit is contained in:
@@ -804,6 +804,52 @@ describe("runCodexAppServerAttempt context-engine lifecycle", () => {
|
||||
await run;
|
||||
});
|
||||
|
||||
it("keeps mirrored history when an inactive per-turn context-engine binding starts fresh", async () => {
|
||||
const sessionFile = path.join(tempDir, "session.jsonl");
|
||||
const workspaceDir = path.join(tempDir, "workspace");
|
||||
const sessionManager = SessionManager.open(sessionFile);
|
||||
sessionManager.appendMessage(userMessage("previous per-turn request", 10) as never);
|
||||
sessionManager.appendMessage(assistantMessage("previous per-turn answer", 11) as never);
|
||||
await writeCodexAppServerBinding(sessionFile, {
|
||||
threadId: "thread-per-turn-context",
|
||||
cwd: workspaceDir,
|
||||
dynamicToolsFingerprint: "[]",
|
||||
contextEngine: {
|
||||
schemaVersion: 1,
|
||||
engineId: "lossless-claw",
|
||||
policyFingerprint:
|
||||
'{"schemaVersion":1,"engineId":"lossless-claw","ownsCompaction":true,"projectionMaxChars":24000}',
|
||||
},
|
||||
});
|
||||
const harness = createStartedThreadHarness(async (method) => {
|
||||
if (method === "thread/start") {
|
||||
return threadStartResult("thread-fresh");
|
||||
}
|
||||
if (method === "thread/resume") {
|
||||
throw new Error("inactive context-engine bindings should start a fresh thread");
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
const params = createParams(sessionFile, workspaceDir);
|
||||
|
||||
const run = runCodexAppServerAttempt(params);
|
||||
await harness.waitForMethod("turn/start");
|
||||
|
||||
expect(harness.requests.map((request) => request.method)).toEqual([
|
||||
"thread/start",
|
||||
"turn/start",
|
||||
]);
|
||||
const inputText = getRequestInputText(harness);
|
||||
expect(inputText).toContain("OpenClaw assembled context for this turn:");
|
||||
expect(inputText).toContain("previous per-turn request");
|
||||
expect(inputText).toContain("previous per-turn answer");
|
||||
expect(inputText).toContain("Current user request:");
|
||||
expect(inputText).toContain("hello");
|
||||
|
||||
await harness.completeTurn("completed", "thread-fresh");
|
||||
await run;
|
||||
});
|
||||
|
||||
it("starts a fresh Codex thread and reprojects when context-engine epoch changes", async () => {
|
||||
const info = vi.spyOn(embeddedAgentLog, "info").mockImplementation(() => undefined);
|
||||
const sessionFile = path.join(tempDir, "session.jsonl");
|
||||
|
||||
@@ -446,11 +446,13 @@ export async function runCodexAppServerAttempt(
|
||||
const activeContextEngine = isActiveHarnessContextEngine(params.contextEngine)
|
||||
? params.contextEngine
|
||||
: undefined;
|
||||
const isInactiveThreadBootstrapBinding = (binding: CodexAppServerThreadBinding | undefined) =>
|
||||
!activeContextEngine && binding?.contextEngine?.projection?.mode === "thread_bootstrap";
|
||||
let startupBinding = await readCodexAppServerBinding(params.sessionFile);
|
||||
preDynamicStartupStages.mark("read-binding");
|
||||
const startupBindingAuthProfileId = startupBinding?.authProfileId;
|
||||
const initialStartupBindingHadInactiveContextEngine =
|
||||
!activeContextEngine && Boolean(startupBinding?.contextEngine);
|
||||
const initialStartupBindingHadInactiveThreadBootstrap =
|
||||
isInactiveThreadBootstrapBinding(startupBinding);
|
||||
startupBinding = await rotateOversizedCodexAppServerStartupBinding({
|
||||
binding: startupBinding,
|
||||
sessionFile: params.sessionFile,
|
||||
@@ -459,8 +461,8 @@ export async function runCodexAppServerAttempt(
|
||||
config: params.config,
|
||||
contextEngineActive: Boolean(activeContextEngine),
|
||||
});
|
||||
const initialInactiveContextEngineBindingForcedFreshStart =
|
||||
initialStartupBindingHadInactiveContextEngine && !startupBinding?.threadId;
|
||||
const initialInactiveThreadBootstrapBindingForcedFreshStart =
|
||||
initialStartupBindingHadInactiveThreadBootstrap && !startupBinding?.threadId;
|
||||
preDynamicStartupStages.mark("rotate-binding");
|
||||
const startupAuthProfileCandidate =
|
||||
params.runtimePlan?.auth.forwardedAuthProfileId ??
|
||||
@@ -688,8 +690,8 @@ export async function runCodexAppServerAttempt(
|
||||
let contextEngineProjection: CodexContextEngineThreadBootstrapProjection | undefined;
|
||||
let precomputedStaleBindingContinuityProjectionApplied = false;
|
||||
let staleBindingContinuityForcedFreshStart = false;
|
||||
let inactiveContextEngineBindingForcedFreshStart =
|
||||
initialInactiveContextEngineBindingForcedFreshStart;
|
||||
let inactiveThreadBootstrapBindingForcedFreshStart =
|
||||
initialInactiveThreadBootstrapBindingForcedFreshStart;
|
||||
const applyFreshThreadContinuityProjection = () => {
|
||||
const projection = projectContextEngineAssemblyForCodex({
|
||||
assembledMessages: historyMessages,
|
||||
@@ -881,8 +883,8 @@ export async function runCodexAppServerAttempt(
|
||||
if (activeContextEngine || !binding?.threadId) {
|
||||
return false;
|
||||
}
|
||||
if (binding.contextEngine) {
|
||||
inactiveContextEngineBindingForcedFreshStart = true;
|
||||
if (isInactiveThreadBootstrapBinding(binding)) {
|
||||
inactiveThreadBootstrapBindingForcedFreshStart = true;
|
||||
return false;
|
||||
}
|
||||
const projected = applyResumeStaleBindingContinuityProjection(binding);
|
||||
@@ -892,7 +894,6 @@ export async function runCodexAppServerAttempt(
|
||||
const applyNoContextEngineContinuityProjection = (
|
||||
action: "started" | "resumed",
|
||||
binding?: CodexAppServerThreadBinding,
|
||||
rotatedContextEngineBinding = false,
|
||||
) => {
|
||||
if (activeContextEngine || !historyMessages.some((message) => message.role === "user")) {
|
||||
return false;
|
||||
@@ -903,12 +904,9 @@ export async function runCodexAppServerAttempt(
|
||||
if (action === "started" && staleBindingContinuityForcedFreshStart) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
action === "started" &&
|
||||
(inactiveContextEngineBindingForcedFreshStart || rotatedContextEngineBinding)
|
||||
) {
|
||||
// A retired or changed context-engine binding already forced Codex onto a
|
||||
// clean native thread; without the engine active, mirrored history would
|
||||
if (action === "started" && inactiveThreadBootstrapBindingForcedFreshStart) {
|
||||
// A retired thread-bootstrap context engine already forced Codex onto a
|
||||
// clean native thread; without that engine active, mirrored history would
|
||||
// re-inject stale bootstrap context as a new user turn.
|
||||
return false;
|
||||
}
|
||||
@@ -929,8 +927,7 @@ export async function runCodexAppServerAttempt(
|
||||
return;
|
||||
}
|
||||
const previousThreadId = startupBinding.threadId;
|
||||
const hadInactiveContextEngineBinding =
|
||||
!activeContextEngine && Boolean(startupBinding.contextEngine);
|
||||
const hadInactiveThreadBootstrapBinding = isInactiveThreadBootstrapBinding(startupBinding);
|
||||
const projectedTurnTokens = estimateCodexAppServerProjectedTurnTokens({
|
||||
prompt: codexTurnPromptText,
|
||||
developerInstructions: buildRenderedCodexDeveloperInstructions(),
|
||||
@@ -947,10 +944,10 @@ export async function runCodexAppServerAttempt(
|
||||
if (startupBinding?.threadId) {
|
||||
return;
|
||||
}
|
||||
inactiveContextEngineBindingForcedFreshStart = hadInactiveContextEngineBinding;
|
||||
inactiveThreadBootstrapBindingForcedFreshStart = hadInactiveThreadBootstrapBinding;
|
||||
staleBindingContinuityForcedFreshStart =
|
||||
precomputedStaleBindingContinuityProjectionApplied &&
|
||||
!inactiveContextEngineBindingForcedFreshStart;
|
||||
!inactiveThreadBootstrapBindingForcedFreshStart;
|
||||
if (activeContextEngine) {
|
||||
contextEngineProjection = undefined;
|
||||
try {
|
||||
@@ -1110,13 +1107,7 @@ export async function runCodexAppServerAttempt(
|
||||
params.abortSignal?.removeEventListener("abort", abortFromUpstream);
|
||||
throw error;
|
||||
}
|
||||
if (
|
||||
applyNoContextEngineContinuityProjection(
|
||||
thread.lifecycle.action,
|
||||
thread,
|
||||
thread.lifecycle.rotatedContextEngineBinding === true,
|
||||
)
|
||||
) {
|
||||
if (applyNoContextEngineContinuityProjection(thread.lifecycle.action, thread)) {
|
||||
await rebuildCodexTurnPromptTextFromCurrentProjection();
|
||||
}
|
||||
trajectoryRecorder?.recordEvent("session.started", {
|
||||
|
||||
Reference in New Issue
Block a user