mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-06 15:18:58 +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
@@ -109,6 +109,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- Onboard/channels: recover externalized channel plugins from stale `channels.<id>` 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 "<channel> 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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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