mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(codex): keep authenticated user identity in agent turns (#121511)
* fix(codex): preserve authenticated sender context * test(codex): type sender recorder fixture * fix(codex): scope sender context to user turns
This commit is contained in:
committed by
GitHub
parent
27d2af2298
commit
c034670b72
@@ -365,6 +365,7 @@ type CodexTurnInterruptParams = JsonObject & {
|
||||
export type CodexTurnStartParams = JsonObject & {
|
||||
threadId: string;
|
||||
input: CodexUserInput[];
|
||||
additionalContext?: Record<string, { kind: "untrusted" | "application"; value: string }>;
|
||||
cwd?: string;
|
||||
model?: string;
|
||||
approvalPolicy?: CodexApprovalPolicy | null;
|
||||
|
||||
@@ -5016,6 +5016,90 @@ describe("runCodexAppServerAttempt", () => {
|
||||
const resumeRequestParams = resumeRequest?.params as Record<string, unknown> | undefined;
|
||||
expect(resumeRequestParams?.developerInstructions).not.toContain(CODEX_GPT5_BEHAVIOR_CONTRACT);
|
||||
});
|
||||
it("sends the current recorded sender on successive turns of one resumed Codex thread", async () => {
|
||||
const { sessionFile, workspaceDir } = createRunPaths();
|
||||
await writeExistingBinding(sessionFile, workspaceDir, { dynamicToolsFingerprint: "[]" });
|
||||
const turnIds = ["turn-ada", "turn-grace"] as const;
|
||||
let nextTurnIndex = 0;
|
||||
const harness = createAppServerHarness(async (method, params) => {
|
||||
if (method === "thread/resume") {
|
||||
return threadStartResult((params as { threadId?: string }).threadId ?? "thread-existing");
|
||||
}
|
||||
if (method === "turn/start") {
|
||||
const turnId = turnIds[nextTurnIndex++];
|
||||
if (!turnId) {
|
||||
throw new Error("unexpected extra turn/start");
|
||||
}
|
||||
return turnStartResult(turnId);
|
||||
}
|
||||
return {};
|
||||
});
|
||||
|
||||
const runTurn = async (sender: { id: string; name: string }, prompt: string, runId: string) => {
|
||||
const expectedTurnStarts =
|
||||
harness.requests.filter((request) => request.method === "turn/start").length + 1;
|
||||
const params = createParams(sessionFile, workspaceDir, { prompt, runId });
|
||||
params.trigger = "user";
|
||||
const message = {
|
||||
role: "user" as const,
|
||||
content: prompt,
|
||||
timestamp: Date.now(),
|
||||
__openclaw: { senderId: sender.id, senderName: sender.name },
|
||||
};
|
||||
params.userTurnTranscriptRecorder = {
|
||||
message,
|
||||
resolveMessage: async () => message,
|
||||
getAdmissionReceipt: () => undefined,
|
||||
markRuntimePersistencePending() {},
|
||||
markRuntimePersisted() {},
|
||||
} as unknown as EmbeddedRunAttemptParams["userTurnTranscriptRecorder"];
|
||||
const run = runCodexAppServerAttempt(params);
|
||||
await vi.waitFor(
|
||||
() =>
|
||||
expect(
|
||||
harness.requests.filter((request) => request.method === "turn/start"),
|
||||
).toHaveLength(expectedTurnStarts),
|
||||
fastWait,
|
||||
);
|
||||
await harness.completeTurn({
|
||||
threadId: "thread-existing",
|
||||
turnId: `turn-${sender.name.toLowerCase()}`,
|
||||
});
|
||||
await run;
|
||||
};
|
||||
|
||||
await runTurn({ id: "profile-ada", name: "Ada" }, "first request", "run-ada");
|
||||
await runTurn({ id: "profile-grace", name: "Grace" }, "second request", "run-grace");
|
||||
|
||||
expect(
|
||||
harness.requests
|
||||
.filter((request) =>
|
||||
["thread/start", "thread/resume", "turn/start"].includes(request.method),
|
||||
)
|
||||
.map((request) => request.method),
|
||||
).toEqual(["thread/resume", "turn/start", "turn/start"]);
|
||||
expect(
|
||||
harness.requests
|
||||
.filter((request) => request.method === "turn/start")
|
||||
.map(
|
||||
(request) =>
|
||||
(
|
||||
request.params as {
|
||||
additionalContext?: Record<string, { kind: string; value: string }>;
|
||||
}
|
||||
).additionalContext?.openclaw_current_sender,
|
||||
),
|
||||
).toEqual([
|
||||
{
|
||||
kind: "untrusted",
|
||||
value: '{"sender":{"id":"profile-ada","name":"Ada"}}',
|
||||
},
|
||||
{
|
||||
kind: "untrusted",
|
||||
value: '{"sender":{"id":"profile-grace","name":"Grace"}}',
|
||||
},
|
||||
]);
|
||||
});
|
||||
it("starts a fresh Codex thread before resume when the native rollout reaches the fallback fuse", async () => {
|
||||
const { sessionFile, workspaceDir, agentDir } = createRunPaths();
|
||||
await writeExistingBinding(sessionFile, workspaceDir, { dynamicToolsFingerprint: "[]" });
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import type { EmbeddedRunAttemptParams } from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { GPT5_HEARTBEAT_PROMPT_OVERLAY as CODEX_GPT5_HEARTBEAT_PROMPT_OVERLAY } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import {
|
||||
asOptionalRecord,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||
import { codexSandboxPolicyForTurn, type CodexAppServerRuntimeOptions } from "./config.js";
|
||||
import type {
|
||||
CodexSandboxPolicy,
|
||||
@@ -14,6 +19,37 @@ import {
|
||||
} from "./thread-model-selection.js";
|
||||
import { buildCodexUserInput } from "./user-input.js";
|
||||
|
||||
const CODEX_CURRENT_SENDER_FIELD_MAX_CHARS = 256;
|
||||
|
||||
function buildCodexCurrentSenderContextValue(params: EmbeddedRunAttemptParams): string | undefined {
|
||||
const metadata = asOptionalRecord(
|
||||
asOptionalRecord(params.userTurnTranscriptRecorder?.message as unknown)?.["__openclaw"],
|
||||
);
|
||||
const recorded = [
|
||||
normalizeOptionalString(metadata?.["senderId"]),
|
||||
normalizeOptionalString(metadata?.["senderName"]),
|
||||
normalizeOptionalString(metadata?.["senderUsername"]),
|
||||
] as const;
|
||||
const [id, name, username] = recorded.some(Boolean)
|
||||
? recorded
|
||||
: [
|
||||
normalizeOptionalString(params.senderId),
|
||||
normalizeOptionalString(params.senderName),
|
||||
normalizeOptionalString(params.senderUsername),
|
||||
];
|
||||
if (!id && !name && !username) {
|
||||
return undefined;
|
||||
}
|
||||
const bound = (value: string) => truncateUtf16Safe(value, CODEX_CURRENT_SENDER_FIELD_MAX_CHARS);
|
||||
return JSON.stringify({
|
||||
sender: {
|
||||
...(id ? { id: bound(id) } : {}),
|
||||
...(name ? { name: bound(name) } : {}),
|
||||
...(username ? { username: bound(username) } : {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function buildTurnStartParams(
|
||||
params: EmbeddedRunAttemptParams,
|
||||
options: {
|
||||
@@ -43,9 +79,16 @@ export function buildTurnStartParams(
|
||||
config: params.config,
|
||||
});
|
||||
const useThreadPermissionProfile = options.appServer.networkProxy && !options.sandboxPolicy;
|
||||
const currentSenderContext =
|
||||
params.trigger === "user" ? buildCodexCurrentSenderContextValue(params) : undefined;
|
||||
// Untrusted context exposes authenticated attribution without promoting human-controlled labels.
|
||||
const additionalContext: CodexTurnStartParams["additionalContext"] = currentSenderContext
|
||||
? { openclaw_current_sender: { kind: "untrusted", value: currentSenderContext } }
|
||||
: undefined;
|
||||
return {
|
||||
threadId: options.threadId,
|
||||
input: buildCodexUserInput(options.promptText ?? params.prompt, params.images),
|
||||
...(additionalContext ? { additionalContext } : {}),
|
||||
cwd: options.cwd,
|
||||
approvalPolicy: options.appServer.approvalPolicy,
|
||||
approvalsReviewer: options.appServer.approvalsReviewer,
|
||||
|
||||
Vendored
+6
@@ -132,6 +132,12 @@
|
||||
|
||||
```json
|
||||
{
|
||||
"additionalContext": {
|
||||
"openclaw_current_sender": {
|
||||
"kind": "untrusted",
|
||||
"value": "{\"sender\":{\"id\":\"424242\",\"name\":\"Pash\",\"username\":\"pash\"}}"
|
||||
}
|
||||
},
|
||||
"approvalPolicy": "never",
|
||||
"approvalsReviewer": "user",
|
||||
"collaborationMode": {
|
||||
|
||||
test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-direct-codex-message-tool.md
Vendored
+6
@@ -132,6 +132,12 @@
|
||||
|
||||
```json
|
||||
{
|
||||
"additionalContext": {
|
||||
"openclaw_current_sender": {
|
||||
"kind": "untrusted",
|
||||
"value": "{\"sender\":{\"id\":\"1000001\",\"name\":\"Pash\",\"username\":\"pash\"}}"
|
||||
}
|
||||
},
|
||||
"approvalPolicy": "never",
|
||||
"approvalsReviewer": "user",
|
||||
"collaborationMode": {
|
||||
|
||||
Reference in New Issue
Block a user