mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 19:20:43 +00:00
fix(codex/app-server): forward bootstrap into developerInstructions (#77372)
The OpenClaw workspace bootstrap block (SOUL.md, IDENTITY.md, USER.md, TOOLS.md, BOOTSTRAP.md, MEMORY.md, HEARTBEAT.md) was only being merged into Codex's config.instructions. The Codex app-server runtime overlay consistently applies the explicit developerInstructions field, so persona and style guidance present in the workspace was failing to shape Codex behavior on session resume. Build the workspace bootstrap block before finalizing developerInstructions and join it into both: - the baseline developerInstructions (initial assignment), and - the context-engine developerInstructions (when context engine is active), preserving the existing config-engine projection addition. The existing config.instructions merge stays intact, so the bootstrap now reaches Codex through both paths and downstream hooks (resolveAgentHarnessBeforePromptBuildResult) see what Codex will actually receive. AGENTS.md remains excluded because Codex loads it natively. Update the existing 'passes OpenClaw bootstrap files through ...' test to also assert the developerInstructions field carries SOUL.md and the Codex AGENTS.md substitution note while still excluding the native AGENTS.md content. Fixes #77363.
This commit is contained in:
committed by
GitHub
parent
af2719a7b9
commit
9edeffc751
@@ -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 () => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user