mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-08 02:52:15 +00:00
fix(codex): respect lifecycle mismatch rotations
This commit is contained in:
@@ -10,7 +10,10 @@ import { AUTH_PROFILE_RUNTIME_CONTRACT } from "openclaw/plugin-sdk/agent-runtime
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { CodexAppServerClientFactory } from "./client-factory.js";
|
||||
import { runCodexAppServerAttempt as runCodexAppServerAttemptImpl } from "./run-attempt.js";
|
||||
import { readCodexAppServerBinding, writeCodexAppServerBinding } from "./session-binding.js";
|
||||
import {
|
||||
readCodexAppServerBinding,
|
||||
writeCodexAppServerBinding as writeRawCodexAppServerBinding,
|
||||
} from "./session-binding.js";
|
||||
import { createCodexTestModel } from "./test-support.js";
|
||||
|
||||
let codexAppServerClientFactoryForTest: CodexAppServerClientFactory | undefined;
|
||||
@@ -58,6 +61,25 @@ function createParams(sessionFile: string, workspaceDir: string): EmbeddedRunAtt
|
||||
} as EmbeddedRunAttemptParams;
|
||||
}
|
||||
|
||||
const DISABLED_CODEX_WEB_SEARCH_THREAD_CONFIG_FINGERPRINT = JSON.stringify({
|
||||
"features.standalone_web_search": false,
|
||||
web_search: "disabled",
|
||||
});
|
||||
|
||||
function writeCodexAppServerBinding(
|
||||
...args: Parameters<typeof writeRawCodexAppServerBinding>
|
||||
) {
|
||||
const [sessionFile, binding, lookup] = args;
|
||||
return writeRawCodexAppServerBinding(
|
||||
sessionFile,
|
||||
{
|
||||
webSearchThreadConfigFingerprint: DISABLED_CODEX_WEB_SEARCH_THREAD_CONFIG_FINGERPRINT,
|
||||
...binding,
|
||||
},
|
||||
lookup,
|
||||
);
|
||||
}
|
||||
|
||||
function threadStartResult(threadId = "thread-auth-contract") {
|
||||
return {
|
||||
thread: {
|
||||
|
||||
@@ -161,7 +161,7 @@ describe("OpenClaw-owned tool runtime contract — Codex app-server adapter", ()
|
||||
expectRecordFields(eventRecord, {
|
||||
toolName: "exec",
|
||||
toolCallId: "call-middleware",
|
||||
args: { command: "status" },
|
||||
args: mergedParams,
|
||||
});
|
||||
expectRecordFields(requireRecord(eventRecord.result, "tool_result middleware result"), {
|
||||
content: [{ type: "text", text: "raw output" }],
|
||||
|
||||
@@ -66,6 +66,25 @@ function createParams(sessionFile: string, workspaceDir: string): EmbeddedRunAtt
|
||||
} as EmbeddedRunAttemptParams;
|
||||
}
|
||||
|
||||
const DISABLED_CODEX_WEB_SEARCH_THREAD_CONFIG_FINGERPRINT = JSON.stringify({
|
||||
"features.standalone_web_search": false,
|
||||
web_search: "disabled",
|
||||
});
|
||||
|
||||
function writeCodexAppServerBinding(
|
||||
...args: Parameters<typeof writeRawCodexAppServerBinding>
|
||||
) {
|
||||
const [sessionFile, binding, lookup] = args;
|
||||
return writeRawCodexAppServerBinding(
|
||||
sessionFile,
|
||||
{
|
||||
webSearchThreadConfigFingerprint: DISABLED_CODEX_WEB_SEARCH_THREAD_CONFIG_FINGERPRINT,
|
||||
...binding,
|
||||
},
|
||||
lookup,
|
||||
);
|
||||
}
|
||||
|
||||
function assistantMessage(text: string, timestamp: number): AgentMessage {
|
||||
return {
|
||||
role: "assistant",
|
||||
|
||||
@@ -18,10 +18,32 @@ import {
|
||||
tempDir,
|
||||
} from "./run-attempt-test-harness.js";
|
||||
import { testing } from "./run-attempt.js";
|
||||
import { readCodexAppServerBinding, writeCodexAppServerBinding } from "./session-binding.js";
|
||||
import {
|
||||
readCodexAppServerBinding,
|
||||
writeCodexAppServerBinding as writeRawCodexAppServerBinding,
|
||||
} from "./session-binding.js";
|
||||
|
||||
setupRunAttemptTestHooks();
|
||||
|
||||
const DISABLED_CODEX_WEB_SEARCH_THREAD_CONFIG_FINGERPRINT = JSON.stringify({
|
||||
"features.standalone_web_search": false,
|
||||
web_search: "disabled",
|
||||
});
|
||||
|
||||
function writeCodexAppServerBinding(
|
||||
...args: Parameters<typeof writeRawCodexAppServerBinding>
|
||||
) {
|
||||
const [sessionFile, binding, lookup] = args;
|
||||
return writeRawCodexAppServerBinding(
|
||||
sessionFile,
|
||||
{
|
||||
webSearchThreadConfigFingerprint: DISABLED_CODEX_WEB_SEARCH_THREAD_CONFIG_FINGERPRINT,
|
||||
...binding,
|
||||
},
|
||||
lookup,
|
||||
);
|
||||
}
|
||||
|
||||
describe("runCodexAppServerAttempt native hook relay", () => {
|
||||
it("registers native hook relay config for an enabled Codex turn and cleans it up", async () => {
|
||||
const sessionFile = path.join(tempDir, "session.jsonl");
|
||||
@@ -609,6 +631,7 @@ describe("runCodexAppServerAttempt native hook relay", () => {
|
||||
cwd: workspaceDir,
|
||||
model: "gpt-5.4-codex",
|
||||
modelProvider: "openai",
|
||||
dynamicToolsFingerprint: "[]",
|
||||
nativeHookRelayGeneration: "generation-from-failed-resume",
|
||||
});
|
||||
const harness = createStartedThreadHarness(async (method) => {
|
||||
|
||||
@@ -38,11 +38,30 @@ import { testing } from "./run-attempt.js";
|
||||
import {
|
||||
readCodexAppServerBinding,
|
||||
resolveCodexAppServerBindingPath,
|
||||
writeCodexAppServerBinding,
|
||||
writeCodexAppServerBinding as writeRawCodexAppServerBinding,
|
||||
} from "./session-binding.js";
|
||||
|
||||
setupRunAttemptTestHooks();
|
||||
|
||||
const DISABLED_CODEX_WEB_SEARCH_THREAD_CONFIG_FINGERPRINT = JSON.stringify({
|
||||
"features.standalone_web_search": false,
|
||||
web_search: "disabled",
|
||||
});
|
||||
|
||||
function writeCodexAppServerBinding(
|
||||
...args: Parameters<typeof writeRawCodexAppServerBinding>
|
||||
) {
|
||||
const [sessionFile, binding, lookup] = args;
|
||||
return writeRawCodexAppServerBinding(
|
||||
sessionFile,
|
||||
{
|
||||
webSearchThreadConfigFingerprint: DISABLED_CODEX_WEB_SEARCH_THREAD_CONFIG_FINGERPRINT,
|
||||
...binding,
|
||||
},
|
||||
lookup,
|
||||
);
|
||||
}
|
||||
|
||||
const tinyPngBase64 =
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=";
|
||||
|
||||
|
||||
@@ -426,20 +426,6 @@ export async function startOrResumeThread(params: {
|
||||
}
|
||||
binding = undefined;
|
||||
}
|
||||
if (
|
||||
binding?.threadId &&
|
||||
params.nativeCodeModeEnabled === false &&
|
||||
!persistentWebSearchRestriction
|
||||
) {
|
||||
embeddedAgentLog.debug(
|
||||
"codex app-server native tool surface disabled for turn; starting transient thread",
|
||||
{
|
||||
threadId: binding.threadId,
|
||||
},
|
||||
);
|
||||
preserveExistingBinding = true;
|
||||
binding = undefined;
|
||||
}
|
||||
if (binding?.threadId && (binding.contextEngine || contextEngineBinding)) {
|
||||
if (
|
||||
!contextEngineBinding ||
|
||||
@@ -589,12 +575,25 @@ export async function startOrResumeThread(params: {
|
||||
);
|
||||
await clearCodexAppServerBinding(params.params.sessionFile);
|
||||
}
|
||||
} else if (
|
||||
params.nativeCodeModeEnabled === false &&
|
||||
!persistentWebSearchRestriction &&
|
||||
binding.dynamicToolsFingerprint !== EMPTY_DYNAMIC_TOOLS_FINGERPRINT
|
||||
) {
|
||||
embeddedAgentLog.debug(
|
||||
"codex app-server native tool surface disabled for turn; starting transient thread",
|
||||
{
|
||||
threadId: binding.threadId,
|
||||
},
|
||||
);
|
||||
preserveExistingBinding = true;
|
||||
} else {
|
||||
const resumeBinding = binding;
|
||||
try {
|
||||
const authProfileId = params.params.authProfileId ?? binding.authProfileId;
|
||||
const authProfileId = params.params.authProfileId ?? resumeBinding.authProfileId;
|
||||
const finalConfigPatch = params.buildFinalConfigPatch?.({
|
||||
action: "resume",
|
||||
binding,
|
||||
binding: resumeBinding,
|
||||
}) ?? {
|
||||
configPatch: params.finalConfigPatch,
|
||||
nativeHookRelayGeneration: params.nativeHookRelayGeneration,
|
||||
@@ -606,7 +605,7 @@ export async function startOrResumeThread(params: {
|
||||
);
|
||||
const resumeParams = lifecycleTiming.measureSync("thread-resume-params", () =>
|
||||
buildThreadResumeParams(params.params, {
|
||||
threadId: binding.threadId,
|
||||
threadId: resumeBinding.threadId,
|
||||
authProfileId,
|
||||
model: startModelSelection.model,
|
||||
modelProvider: startModelProvider,
|
||||
@@ -634,7 +633,7 @@ export async function startOrResumeThread(params: {
|
||||
const nextMcpServersFingerprint =
|
||||
params.mcpServersFingerprintEvaluated === true
|
||||
? params.mcpServersFingerprint
|
||||
: binding.mcpServersFingerprint;
|
||||
: resumeBinding.mcpServersFingerprint;
|
||||
await lifecycleTiming.measure("thread-resume-write-binding", () =>
|
||||
writeCodexAppServerBinding(
|
||||
params.params.sessionFile,
|
||||
@@ -650,13 +649,14 @@ export async function startOrResumeThread(params: {
|
||||
userMcpServersFingerprint,
|
||||
mcpServersFingerprint: nextMcpServersFingerprint,
|
||||
nativeHookRelayGeneration:
|
||||
finalConfigPatch.nativeHookRelayGeneration ?? binding.nativeHookRelayGeneration,
|
||||
pluginAppsFingerprint: binding.pluginAppsFingerprint,
|
||||
pluginAppsInputFingerprint: binding.pluginAppsInputFingerprint,
|
||||
pluginAppPolicyContext: binding.pluginAppPolicyContext,
|
||||
finalConfigPatch.nativeHookRelayGeneration ??
|
||||
resumeBinding.nativeHookRelayGeneration,
|
||||
pluginAppsFingerprint: resumeBinding.pluginAppsFingerprint,
|
||||
pluginAppsInputFingerprint: resumeBinding.pluginAppsInputFingerprint,
|
||||
pluginAppPolicyContext: resumeBinding.pluginAppPolicyContext,
|
||||
contextEngine: contextEngineBinding,
|
||||
environmentSelectionFingerprint,
|
||||
createdAt: binding.createdAt,
|
||||
createdAt: resumeBinding.createdAt,
|
||||
},
|
||||
{
|
||||
authProfileStore: params.params.authProfileStore,
|
||||
@@ -686,7 +686,7 @@ export async function startOrResumeThread(params: {
|
||||
});
|
||||
const activeTurnIds = readActiveCodexTurnIds(response.thread);
|
||||
return {
|
||||
...binding,
|
||||
...resumeBinding,
|
||||
threadId: response.thread.id,
|
||||
cwd: params.cwd,
|
||||
authProfileId: boundAuthProfileId,
|
||||
@@ -698,10 +698,10 @@ export async function startOrResumeThread(params: {
|
||||
userMcpServersFingerprint,
|
||||
mcpServersFingerprint: nextMcpServersFingerprint,
|
||||
nativeHookRelayGeneration:
|
||||
finalConfigPatch.nativeHookRelayGeneration ?? binding.nativeHookRelayGeneration,
|
||||
pluginAppsFingerprint: binding.pluginAppsFingerprint,
|
||||
pluginAppsInputFingerprint: binding.pluginAppsInputFingerprint,
|
||||
pluginAppPolicyContext: binding.pluginAppPolicyContext,
|
||||
finalConfigPatch.nativeHookRelayGeneration ?? resumeBinding.nativeHookRelayGeneration,
|
||||
pluginAppsFingerprint: resumeBinding.pluginAppsFingerprint,
|
||||
pluginAppsInputFingerprint: resumeBinding.pluginAppsInputFingerprint,
|
||||
pluginAppPolicyContext: resumeBinding.pluginAppPolicyContext,
|
||||
contextEngine: contextEngineBinding,
|
||||
environmentSelectionFingerprint,
|
||||
lifecycle: {
|
||||
|
||||
@@ -4,10 +4,7 @@ import { resetAdjustedParamsByToolCallIdForTests } from "../../../agents/agent-t
|
||||
import type { AgentToolResult } from "../../../agents/runtime/index.js";
|
||||
import { setToolTerminalPresentation } from "../../../agents/tool-terminal-presentation.js";
|
||||
import type { AnyAgentTool } from "../../../agents/tools/common.js";
|
||||
import type {
|
||||
CodexAppServerExtensionFactory,
|
||||
CodexAppServerToolResultEvent,
|
||||
} from "../../../plugins/codex-app-server-extension-types.js";
|
||||
import type { AgentToolResultMiddlewareEvent } from "../../../plugins/agent-tool-result-middleware-types.js";
|
||||
import {
|
||||
initializeGlobalHookRunner,
|
||||
resetGlobalHookRunner,
|
||||
@@ -90,20 +87,18 @@ export function installOpenClawOwnedToolHooks(params?: {
|
||||
* Pair with `installOpenClawOwnedToolHooks()` when a test asserts before/after hook behavior.
|
||||
*/
|
||||
export function installCodexToolResultMiddleware(
|
||||
handler: (event: CodexAppServerToolResultEvent) => AgentToolResult<unknown>,
|
||||
handler: (event: AgentToolResultMiddlewareEvent) => AgentToolResult<unknown>,
|
||||
) {
|
||||
const middleware = vi.fn(async (event: CodexAppServerToolResultEvent) => ({
|
||||
const middleware = vi.fn(async (event: AgentToolResultMiddlewareEvent) => ({
|
||||
result: handler(event),
|
||||
}));
|
||||
const registry = createEmptyPluginRegistry();
|
||||
const factory: CodexAppServerExtensionFactory = async (codex) => {
|
||||
codex.on("tool_result", middleware);
|
||||
};
|
||||
registry.codexAppServerExtensionFactories.push({
|
||||
registry.agentToolResultMiddlewares.push({
|
||||
pluginId: "runtime-contract",
|
||||
pluginName: "Runtime Contract",
|
||||
rawFactory: factory,
|
||||
factory,
|
||||
rawHandler: middleware,
|
||||
handler: middleware,
|
||||
runtimes: ["codex"],
|
||||
source: "test",
|
||||
});
|
||||
setActivePluginRegistry(registry);
|
||||
|
||||
Reference in New Issue
Block a user