diff --git a/CHANGELOG.md b/CHANGELOG.md index 965aca3518b..f5c95288d36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -109,6 +109,7 @@ Docs: https://docs.openclaw.ai ### Fixes - Onboard/channels: recover externalized channel plugins from stale `channels.` config by falling back to `ensureChannelSetupPluginInstalled` via the trusted catalog when the plugin is missing on disk, so leftover `appId`/token entries no longer dead-end onboard with " plugin not available." (#78328) Thanks @sliverp. +- Codex/app-server: forward the OpenClaw workspace bootstrap block through Codex `developerInstructions` instead of `config.instructions`, so persona/style guidance reaches the behavior-shaping app-server lane. Fixes #77363. Thanks @lonexreb. - Dependencies: override transitive `ip-address` to `10.2.0` so the runtime lockfile no longer includes the vulnerable `10.1.0` build flagged by Dependabot alert 109. Thanks @vincentkoc. - Feishu: hydrate missing native topic starter thread IDs before session routing so first turns and follow-ups stay in the same topic session. Fixes #78262. Thanks @joeyzenghuan. - LINE: reject `dmPolicy: "open"` configs without wildcard `allowFrom` so webhook DMs fail validation instead of being acknowledged and silently blocked before inbound processing. Fixes #78316. diff --git a/docs/plugins/codex-harness.md b/docs/plugins/codex-harness.md index 4777ce499d6..3226ad3d84f 100644 --- a/docs/plugins/codex-harness.md +++ b/docs/plugins/codex-harness.md @@ -274,9 +274,9 @@ filenames for persona files, because Codex fallbacks only apply when For OpenClaw workspace parity, the Codex harness resolves the other bootstrap files (`SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, `BOOTSTRAP.md`, and `MEMORY.md` when present) and forwards them through Codex -config instructions on `thread/start` and `thread/resume`. This keeps -`SOUL.md` and related workspace persona/profile context visible without -duplicating `AGENTS.md`. +developer instructions on `thread/start` and `thread/resume`. This keeps +`SOUL.md` and related workspace persona/profile context visible on the native +Codex behavior-shaping lane without duplicating `AGENTS.md`. ## Add Codex alongside other models diff --git a/extensions/codex/src/app-server/run-attempt.test.ts b/extensions/codex/src/app-server/run-attempt.test.ts index 542b3023951..8245373afa8 100644 --- a/extensions/codex/src/app-server/run-attempt.test.ts +++ b/extensions/codex/src/app-server/run-attempt.test.ts @@ -952,7 +952,7 @@ describe("runCodexAppServerAttempt", () => { expect(inputText).toContain("make the default webpage openclaw"); }); - it("passes OpenClaw bootstrap files through Codex config instructions", async () => { + it("passes OpenClaw bootstrap files through Codex developer instructions", async () => { const sessionFile = path.join(tempDir, "session.jsonl"); const workspaceDir = path.join(tempDir, "workspace"); await fs.mkdir(workspaceDir, { recursive: true }); @@ -967,14 +967,18 @@ describe("runCodexAppServerAttempt", () => { await run; const threadStart = harness.requests.find((request) => request.method === "thread/start"); - const config = (threadStart?.params as { config?: { instructions?: string } }).config; - expect(config).toEqual( - expect.objectContaining({ - instructions: expect.stringContaining("Soul voice goes here."), - }), - ); - expect(config?.instructions).toContain("Codex loads AGENTS.md natively"); - expect(config?.instructions).not.toContain("Follow AGENTS guidance."); + const params = threadStart?.params as { + config?: { instructions?: string }; + developerInstructions?: string; + }; + const config = params.config; + + // Regression for #77363: persona/style bootstrap (SOUL.md) must reach the + // explicit developerInstructions field, not config.instructions. + expect(params.developerInstructions).toContain("Soul voice goes here."); + expect(params.developerInstructions).toContain("Codex loads AGENTS.md natively"); + expect(params.developerInstructions).not.toContain("Follow AGENTS guidance."); + expect(config?.instructions).toBeUndefined(); }); it("fires llm_input, llm_output, and agent_end hooks for codex turns", async () => { diff --git a/extensions/codex/src/app-server/run-attempt.ts b/extensions/codex/src/app-server/run-attempt.ts index 200d6070610..9bd300877bb 100644 --- a/extensions/codex/src/app-server/run-attempt.ts +++ b/extensions/codex/src/app-server/run-attempt.ts @@ -484,8 +484,21 @@ export async function runCodexAppServerAttempt( (await readMirroredSessionHistoryMessages(params.sessionFile)) ?? historyMessages; } const baseDeveloperInstructions = buildDeveloperInstructions(params); + // Build the workspace bootstrap block before finalizing developer + // instructions so persona files (SOUL.md, IDENTITY.md, ...) reach Codex + // through the explicit `developerInstructions` field. + const workspaceBootstrapInstructions = await buildCodexWorkspaceBootstrapInstructions({ + params, + resolvedWorkspace, + effectiveWorkspace, + sessionKey: sandboxSessionKey, + sessionAgentId, + }); let promptText = params.prompt; - let developerInstructions = baseDeveloperInstructions; + let developerInstructions = joinPresentSections( + baseDeveloperInstructions, + workspaceBootstrapInstructions, + ); let prePromptMessageCount = historyMessages.length; if (activeContextEngine) { try { @@ -512,6 +525,7 @@ export async function runCodexAppServerAttempt( promptText = projection.promptText; developerInstructions = joinPresentSections( baseDeveloperInstructions, + workspaceBootstrapInstructions, projection.developerInstructionAddition, ); prePromptMessageCount = projection.prePromptMessageCount; @@ -541,13 +555,6 @@ export async function runCodexAppServerAttempt( messages: historyMessages, ctx: hookContext, }); - const workspaceBootstrapInstructions = await buildCodexWorkspaceBootstrapInstructions({ - params, - resolvedWorkspace, - effectiveWorkspace, - sessionKey: sandboxSessionKey, - sessionAgentId, - }); const trajectoryRecorder = createCodexTrajectoryRecorder({ attempt: params, cwd: effectiveWorkspace, @@ -583,10 +590,7 @@ export async function runCodexAppServerAttempt( : options.nativeHookRelay?.enabled === false ? buildCodexNativeHookRelayDisabledConfig() : undefined; - const threadConfig = mergeCodexConfigInstructions( - nativeHookRelayConfig, - workspaceBootstrapInstructions, - ); + const threadConfig = nativeHookRelayConfig; ({ client, thread } = await withCodexStartupTimeout({ timeoutMs: params.timeoutMs, timeoutFloorMs: options.startupTimeoutFloorMs, @@ -1871,20 +1875,6 @@ function renderCodexWorkspaceBootstrapInstructions( return lines.join("\n").trim(); } -function mergeCodexConfigInstructions( - config: JsonObject | undefined, - instructions: string | undefined, -): JsonObject | undefined { - if (!instructions?.trim()) { - return config; - } - const merged: JsonObject = { ...config }; - const existingInstructions = - typeof merged.instructions === "string" ? merged.instructions.trim() : undefined; - merged.instructions = joinPresentSections(existingInstructions, instructions); - return merged; -} - function remapCodexContextFilePath(params: { file: EmbeddedContextFile; sourceWorkspaceDir: string;