mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(codex): keep sessionless mirror warnings out of onboarding (#122831)
* fix(codex): skip mirrors for sessionless runs
* fix(codex): restore openai model provider on media understanding turns
PR #122163 made thread/start modelProvider caller-supplied and updated the
web-search caller but missed media understanding, breaking its tests on main
full runs (cross-lane gap). Pass the provider explicitly and export the
retire binding prod now touches from the shared-client test mock.
* test(codex): align prompt assertions with reworded guidance
PR #121522 reworded the Skill Workshop guidance and cd7b7f639d reworded
the message-tool final-reply text; both updated core tests but missed
these codex mirror assertions (cross-lane gap breaking main full runs).
* test(codex): scope agent-projection fixture session to its agent
PR #114388 made multi-agent session ownership explicit; the atlas-scoped
projection test still used the shared main-scoped session key and now
trips AgentSelectionRequiredError (fourth cross-lane escape on main).
This commit is contained in:
committed by
GitHub
parent
add6b20bd2
commit
469be48967
@@ -12,6 +12,7 @@ const sharedClientMocks = vi.hoisted(() => ({
|
||||
|
||||
vi.mock("./src/app-server/shared-client.js", () => ({
|
||||
createIsolatedCodexAppServerClient: sharedClientMocks.createIsolatedCodexAppServerClient,
|
||||
retireSharedCodexAppServerClientIfCurrent: () => undefined,
|
||||
}));
|
||||
|
||||
function codexModel(inputModalities: string[] = ["text", "image"]) {
|
||||
|
||||
@@ -76,6 +76,7 @@ async function describeCodexImages(
|
||||
const { text } = await runBoundedCodexAppServerTurn({
|
||||
config: req.cfg,
|
||||
model: { mode: "required", id: model },
|
||||
modelProvider: "openai",
|
||||
profile: req.profile,
|
||||
timeoutMs: req.timeoutMs,
|
||||
signal: req.signal,
|
||||
@@ -121,6 +122,7 @@ async function extractCodexStructured(
|
||||
const { text } = await runBoundedCodexAppServerTurn({
|
||||
config: req.cfg,
|
||||
model: { mode: "required", id: model },
|
||||
modelProvider: "openai",
|
||||
profile: req.profile,
|
||||
timeoutMs: req.timeoutMs,
|
||||
signal: req.signal,
|
||||
|
||||
@@ -1048,7 +1048,7 @@ describe("Codex app-server native code mode config", () => {
|
||||
expect(instructions).toContain("## Skill Workshop");
|
||||
expect(instructions).toContain("Durable reusable skill/playbook/workflow work");
|
||||
expect(instructions).toContain("`skill_workshop`");
|
||||
expect(instructions).toContain("Generated = pending proposal");
|
||||
expect(instructions).toContain("Other generated work = pending proposal");
|
||||
expect(instructions).toContain("only explicit user ask");
|
||||
});
|
||||
|
||||
@@ -1083,7 +1083,7 @@ describe("Codex app-server native code mode config", () => {
|
||||
});
|
||||
|
||||
expect(instructions).toContain("For progress, set `final=false`.");
|
||||
expect(instructions).toContain("set `final=true`");
|
||||
expect(instructions).toContain("Set `final=true`, or omit it,");
|
||||
});
|
||||
|
||||
it("keeps durable dynamic tool fingerprints scoped to loading mode", () => {
|
||||
|
||||
@@ -197,6 +197,7 @@ describe("startOrResumeThread — user mcp.servers projection (regression: #8081
|
||||
|
||||
it("projects only Codex user MCP servers scoped to the current agent", async () => {
|
||||
const sessionFile = path.join(tempDir, "session.jsonl");
|
||||
registerCodexTestSessionIdentity(sessionFile, "session-1", "agent:atlas:session-1");
|
||||
const workspaceDir = path.join(tempDir, "workspace");
|
||||
const request = vi.fn(async (method: string, _params: unknown) => {
|
||||
if (method === "thread/start") {
|
||||
@@ -207,28 +208,33 @@ describe("startOrResumeThread — user mcp.servers projection (regression: #8081
|
||||
|
||||
await startOrResumeThread({
|
||||
client: { request } as never,
|
||||
params: createParams(sessionFile, workspaceDir, {
|
||||
mcp: {
|
||||
servers: {
|
||||
atlas: {
|
||||
transport: "streamable-http",
|
||||
url: "https://atlas.example.com/mcp",
|
||||
codex: {
|
||||
agents: ["atlas"],
|
||||
defaultToolsApprovalMode: "approve",
|
||||
params: {
|
||||
...createParams(sessionFile, workspaceDir, {
|
||||
mcp: {
|
||||
servers: {
|
||||
atlas: {
|
||||
transport: "streamable-http",
|
||||
url: "https://atlas.example.com/mcp",
|
||||
codex: {
|
||||
agents: ["atlas"],
|
||||
defaultToolsApprovalMode: "approve",
|
||||
},
|
||||
},
|
||||
},
|
||||
apolo: {
|
||||
transport: "streamable-http",
|
||||
url: "https://apolo.example.com/mcp",
|
||||
codex: {
|
||||
agents: ["apolo"],
|
||||
defaultToolsApprovalMode: "approve",
|
||||
apolo: {
|
||||
transport: "streamable-http",
|
||||
url: "https://apolo.example.com/mcp",
|
||||
codex: {
|
||||
agents: ["apolo"],
|
||||
defaultToolsApprovalMode: "approve",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as EmbeddedRunAttemptParams["config"]),
|
||||
} as unknown as EmbeddedRunAttemptParams["config"]),
|
||||
// Explicit multi-agent ownership (#114388): the session key owner must
|
||||
// match the explicit agentId below.
|
||||
sessionKey: "agent:atlas:session-1",
|
||||
},
|
||||
agentId: "atlas",
|
||||
cwd: workspaceDir,
|
||||
dynamicTools: [],
|
||||
|
||||
@@ -4,7 +4,7 @@ import { createHash } from "node:crypto";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import type { AgentMessage } from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { embeddedAgentLog, type AgentMessage } from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import {
|
||||
initializeGlobalHookRunner,
|
||||
resetGlobalHookRunner,
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
buildCodexUserPromptMessage,
|
||||
codexTranscriptMirrorRuntime,
|
||||
importCodexThreadHistoryToTranscript,
|
||||
mirrorPromptAtTurnStartBestEffort,
|
||||
projectBoundedCodexThreadHistory,
|
||||
} from "./transcript-mirror.js";
|
||||
import { attachCodexMirrorIdentity } from "./upstream-prompt-provenance.js";
|
||||
@@ -1275,8 +1276,10 @@ describe("mirrorCodexAppServerTranscript", () => {
|
||||
expect(await readMirrorMessages(target)).toEqual([]);
|
||||
});
|
||||
|
||||
it("leaves the assistant unowned when transcript persistence fails", async () => {
|
||||
it("skips transcript mirrors for sessionless embedded runs", async () => {
|
||||
const root = await makeRoot("openclaw-codex-transcript-failure-");
|
||||
const warn = vi.spyOn(embeddedAgentLog, "warn").mockImplementation(() => undefined);
|
||||
const markRuntimePersistencePending = vi.fn();
|
||||
const assistantMessage = attachCodexMirrorIdentity(
|
||||
makeAgentAssistantMessage({
|
||||
content: [{ type: "text", text: "needs fallback persistence" }],
|
||||
@@ -1285,14 +1288,31 @@ describe("mirrorCodexAppServerTranscript", () => {
|
||||
"turn-1:assistant",
|
||||
);
|
||||
|
||||
const params = {
|
||||
prompt: "sessionless prompt",
|
||||
runId: "probe-setup-inference-sessionless",
|
||||
sessionId: "session-1",
|
||||
userTurnTranscriptRecorder: {
|
||||
markRuntimePersistencePending,
|
||||
resolveMessage: async () => undefined,
|
||||
},
|
||||
} as unknown as Parameters<typeof mirrorTranscriptBestEffort>[0]["params"];
|
||||
|
||||
await mirrorPromptAtTurnStartBestEffort({
|
||||
params,
|
||||
sessionKey: "agent:main:setup-inference:incognito-session-1",
|
||||
notifyUserMessagePersisted: () => undefined,
|
||||
cwd: root,
|
||||
threadId: "thread-1",
|
||||
turnId: "turn-1",
|
||||
upstreamUserText: "sessionless prompt",
|
||||
});
|
||||
const mirrorOutcome = await mirrorTranscriptBestEffort({
|
||||
params: {
|
||||
sessionId: "session-1",
|
||||
suppressNextUserMessagePersistence: true,
|
||||
} as unknown as Parameters<typeof mirrorTranscriptBestEffort>[0]["params"],
|
||||
params,
|
||||
result: {
|
||||
messagesSnapshot: [assistantMessage],
|
||||
} as Parameters<typeof mirrorTranscriptBestEffort>[0]["result"],
|
||||
sessionKey: "agent:main:setup-inference:incognito-session-1",
|
||||
notifyUserMessagePersisted: () => undefined,
|
||||
cwd: root,
|
||||
threadId: "thread-1",
|
||||
@@ -1300,6 +1320,66 @@ describe("mirrorCodexAppServerTranscript", () => {
|
||||
});
|
||||
|
||||
expect(mirrorOutcome).toEqual({ assistantTranscriptOwned: false, mirroredMessages: [] });
|
||||
expect(markRuntimePersistencePending).not.toHaveBeenCalled();
|
||||
expect(warn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("renders normal-session mirror failures in structured warnings", async () => {
|
||||
const root = await makeRoot("openclaw-codex-transcript-failure-");
|
||||
const blockedParent = path.join(root, "not-a-directory");
|
||||
await fs.writeFile(blockedParent, "blocked");
|
||||
const storePath = path.join(blockedParent, "openclaw-agent.sqlite");
|
||||
const warn = vi.spyOn(embeddedAgentLog, "warn").mockImplementation(() => undefined);
|
||||
warn.mockClear();
|
||||
const runId = "run-1";
|
||||
const sessionId = "session-1";
|
||||
const params = {
|
||||
prompt: "persist me",
|
||||
runId,
|
||||
sessionId,
|
||||
sessionTarget: { storePath },
|
||||
} as unknown as Parameters<typeof mirrorPromptAtTurnStartBestEffort>[0]["params"];
|
||||
|
||||
await mirrorPromptAtTurnStartBestEffort({
|
||||
params,
|
||||
sessionKey: "agent:main:session-1",
|
||||
notifyUserMessagePersisted: () => undefined,
|
||||
cwd: storePath,
|
||||
threadId: "thread-1",
|
||||
turnId: "turn-1",
|
||||
upstreamUserText: "persist me",
|
||||
});
|
||||
|
||||
expect(warn).toHaveBeenCalledWith("failed to mirror codex app-server prompt at turn start", {
|
||||
error: expect.any(String),
|
||||
runId,
|
||||
sessionId,
|
||||
});
|
||||
const warning = warn.mock.calls.at(-1)?.[1] as { error?: string } | undefined;
|
||||
expect(warning?.error).not.toBe("");
|
||||
|
||||
warn.mockClear();
|
||||
await mirrorTranscriptBestEffort({
|
||||
params,
|
||||
result: {
|
||||
messagesSnapshot: [
|
||||
makeAgentAssistantMessage({
|
||||
content: [{ type: "text", text: "persist me too" }],
|
||||
timestamp: Date.now(),
|
||||
}),
|
||||
],
|
||||
} as Parameters<typeof mirrorTranscriptBestEffort>[0]["result"],
|
||||
sessionKey: "agent:main:session-1",
|
||||
notifyUserMessagePersisted: () => undefined,
|
||||
cwd: root,
|
||||
threadId: "thread-1",
|
||||
turnId: "turn-1",
|
||||
});
|
||||
expect(warn).toHaveBeenCalledWith("failed to mirror codex app-server transcript", {
|
||||
error: expect.any(String),
|
||||
runId,
|
||||
sessionId,
|
||||
});
|
||||
});
|
||||
|
||||
it("does not attest a stale idempotency hit with the same mirror identity", async () => {
|
||||
|
||||
@@ -112,6 +112,9 @@ async function mirrorBestEffort(params: {
|
||||
terminalAnchor?: TranscriptEntryAnchor;
|
||||
mirroredMessages: MirroredAgentMessage[];
|
||||
}> {
|
||||
if (!params.params.sessionTarget) {
|
||||
return { assistantTranscriptOwned: false, mirroredMessages: [] };
|
||||
}
|
||||
try {
|
||||
const messages = await resolveFinalCodexMirrorMessages({
|
||||
params: params.params,
|
||||
@@ -182,7 +185,11 @@ async function mirrorBestEffort(params: {
|
||||
mirroredMessages,
|
||||
};
|
||||
} catch (error) {
|
||||
embeddedAgentLog.warn("failed to mirror codex app-server transcript", { error });
|
||||
embeddedAgentLog.warn("failed to mirror codex app-server transcript", {
|
||||
error: formatErrorMessage(error),
|
||||
runId: params.params.runId,
|
||||
sessionId: params.params.sessionId,
|
||||
});
|
||||
return { assistantTranscriptOwned: false, mirroredMessages: [] };
|
||||
}
|
||||
}
|
||||
@@ -249,7 +256,7 @@ export async function mirrorPromptAtTurnStartBestEffort(params: {
|
||||
turnId: string;
|
||||
upstreamUserText: string;
|
||||
}): Promise<void> {
|
||||
if (params.params.suppressNextUserMessagePersistence) {
|
||||
if (params.params.suppressNextUserMessagePersistence || !params.params.sessionTarget) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -281,7 +288,11 @@ export async function mirrorPromptAtTurnStartBestEffort(params: {
|
||||
params.params.userTurnTranscriptRecorder?.markRuntimePersistencePending(mirrorPromise);
|
||||
await mirrorPromise;
|
||||
} catch (error) {
|
||||
embeddedAgentLog.warn("failed to mirror codex app-server prompt at turn start", { error });
|
||||
embeddedAgentLog.warn("failed to mirror codex app-server prompt at turn start", {
|
||||
error: formatErrorMessage(error),
|
||||
runId: params.params.runId,
|
||||
sessionId: params.params.sessionId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user