


























@@ -1,5 +1,6 @@
11import { createHash } from "node:crypto";
22import fs from "node:fs/promises";
3+import path from "node:path";
34import {
45assembleHarnessContextEngine,
56bootstrapHarnessContextEngine,
@@ -26,12 +27,14 @@ import {
2627runAgentHarnessLlmOutputHook,
2728runHarnessContextEngineMaintenance,
2829registerNativeHookRelay,
30+resolveBootstrapContextForRun,
2931setActiveEmbeddedRun,
3032supportsModelTools,
3133runAgentCleanupStep,
3234type AgentMessage,
3335type EmbeddedRunAttemptParams,
3436type EmbeddedRunAttemptResult,
37+type EmbeddedContextFile,
3538type NativeHookRelayEvent,
3639type NativeHookRelayRegistrationHandle,
3740} from "openclaw/plugin-sdk/agent-harness-runtime";
@@ -101,6 +104,16 @@ const CODEX_TURN_COMPLETION_IDLE_TIMEOUT_MS = 60_000;
101104const CODEX_TURN_TERMINAL_IDLE_TIMEOUT_MS = 30 * 60_000;
102105const CODEX_STEER_ALL_DEBOUNCE_MS = 500;
103106const LOG_FIELD_MAX_LENGTH = 160;
107+const CODEX_NATIVE_PROJECT_DOC_BASENAMES = new Set(["agents.md"]);
108+const CODEX_BOOTSTRAP_CONTEXT_ORDER = new Map<string, number>([
109+["soul.md", 10],
110+["identity.md", 20],
111+["user.md", 30],
112+["tools.md", 40],
113+["bootstrap.md", 50],
114+["memory.md", 60],
115+["heartbeat.md", 70],
116+]);
104117105118type OpenClawCodingToolsOptions = NonNullable<
106119Parameters<(typeof import("openclaw/plugin-sdk/agent-harness"))["createOpenClawCodingTools"]>[0]
@@ -472,6 +485,13 @@ export async function runCodexAppServerAttempt(
472485messages: historyMessages,
473486ctx: hookContext,
474487});
488+const workspaceBootstrapInstructions = await buildCodexWorkspaceBootstrapInstructions({
489+ params,
490+ resolvedWorkspace,
491+ effectiveWorkspace,
492+sessionKey: sandboxSessionKey,
493+ sessionAgentId,
494+});
475495const trajectoryRecorder = createCodexTrajectoryRecorder({
476496attempt: params,
477497cwd: effectiveWorkspace,
@@ -506,6 +526,10 @@ export async function runCodexAppServerAttempt(
506526 : options.nativeHookRelay?.enabled === false
507527 ? buildCodexNativeHookRelayDisabledConfig()
508528 : undefined;
529+const threadConfig = mergeCodexConfigInstructions(
530+nativeHookRelayConfig,
531+workspaceBootstrapInstructions,
532+);
509533({ client, thread } = await withCodexStartupTimeout({
510534timeoutMs: params.timeoutMs,
511535timeoutFloorMs: options.startupTimeoutFloorMs,
@@ -533,7 +557,7 @@ export async function runCodexAppServerAttempt(
533557dynamicTools: toolBridge.specs,
534558 appServer,
535559developerInstructions: promptBuild.developerInstructions,
536-config: nativeHookRelayConfig,
560+config: threadConfig,
537561});
538562return { client: startupClient, thread: startupThread };
539563};
@@ -1597,6 +1621,129 @@ async function readMirroredSessionHistoryMessages(
15971621return messages;
15981622}
159916231624+async function buildCodexWorkspaceBootstrapInstructions(params: {
1625+params: EmbeddedRunAttemptParams;
1626+resolvedWorkspace: string;
1627+effectiveWorkspace: string;
1628+sessionKey: string;
1629+sessionAgentId: string;
1630+}): Promise<string | undefined> {
1631+try {
1632+const { contextFiles } = await resolveBootstrapContextForRun({
1633+workspaceDir: params.resolvedWorkspace,
1634+config: params.params.config,
1635+sessionKey: params.sessionKey,
1636+sessionId: params.params.sessionId,
1637+agentId: params.params.agentId ?? params.sessionAgentId,
1638+warn: (message) => embeddedAgentLog.warn(message),
1639+contextMode: params.params.bootstrapContextMode,
1640+runKind: params.params.bootstrapContextRunKind,
1641+});
1642+return renderCodexWorkspaceBootstrapInstructions(
1643+contextFiles.map((file) =>
1644+remapCodexContextFilePath({
1645+ file,
1646+sourceWorkspaceDir: params.resolvedWorkspace,
1647+targetWorkspaceDir: params.effectiveWorkspace,
1648+}),
1649+),
1650+);
1651+} catch (error) {
1652+embeddedAgentLog.warn("failed to load codex workspace bootstrap instructions", { error });
1653+return undefined;
1654+}
1655+}
1656+1657+function renderCodexWorkspaceBootstrapInstructions(
1658+contextFiles: EmbeddedContextFile[],
1659+): string | undefined {
1660+const files = contextFiles
1661+.filter((file) => {
1662+const baseName = getCodexContextFileBasename(file.path);
1663+return baseName && !CODEX_NATIVE_PROJECT_DOC_BASENAMES.has(baseName);
1664+})
1665+.toSorted(compareCodexContextFiles);
1666+if (files.length === 0) {
1667+return undefined;
1668+}
1669+const hasSoulFile = files.some((file) => getCodexContextFileBasename(file.path) === "soul.md");
1670+const lines = [
1671+"OpenClaw loaded these user-editable workspace files. Treat them as project/user context. Codex loads AGENTS.md natively, so AGENTS.md is not repeated here.",
1672+"",
1673+"# Project Context",
1674+"",
1675+"The following project context files have been loaded:",
1676+];
1677+if (hasSoulFile) {
1678+lines.push(
1679+"If SOUL.md is present, embody its persona and tone. Avoid stiff, generic replies; follow its guidance unless higher-priority instructions override it.",
1680+);
1681+}
1682+lines.push("");
1683+for (const file of files) {
1684+lines.push(`## ${file.path}`, "", file.content, "");
1685+}
1686+return lines.join("\n").trim();
1687+}
1688+1689+function mergeCodexConfigInstructions(
1690+config: JsonObject | undefined,
1691+instructions: string | undefined,
1692+): JsonObject | undefined {
1693+if (!instructions?.trim()) {
1694+return config;
1695+}
1696+const merged: JsonObject = { ...config };
1697+const existingInstructions =
1698+typeof merged.instructions === "string" ? merged.instructions.trim() : undefined;
1699+merged.instructions = joinPresentSections(existingInstructions, instructions);
1700+return merged;
1701+}
1702+1703+function remapCodexContextFilePath(params: {
1704+file: EmbeddedContextFile;
1705+sourceWorkspaceDir: string;
1706+targetWorkspaceDir: string;
1707+}): EmbeddedContextFile {
1708+const relativePath = path.relative(params.sourceWorkspaceDir, params.file.path);
1709+if (
1710+!relativePath ||
1711+relativePath.startsWith("..") ||
1712+path.isAbsolute(relativePath) ||
1713+params.sourceWorkspaceDir === params.targetWorkspaceDir
1714+) {
1715+return params.file;
1716+}
1717+return {
1718+ ...params.file,
1719+path: path.join(params.targetWorkspaceDir, relativePath),
1720+};
1721+}
1722+1723+function compareCodexContextFiles(left: EmbeddedContextFile, right: EmbeddedContextFile): number {
1724+const leftPath = normalizeCodexContextFilePath(left.path);
1725+const rightPath = normalizeCodexContextFilePath(right.path);
1726+const leftBase = getCodexContextFileBasename(left.path);
1727+const rightBase = getCodexContextFileBasename(right.path);
1728+const leftOrder = CODEX_BOOTSTRAP_CONTEXT_ORDER.get(leftBase) ?? Number.MAX_SAFE_INTEGER;
1729+const rightOrder = CODEX_BOOTSTRAP_CONTEXT_ORDER.get(rightBase) ?? Number.MAX_SAFE_INTEGER;
1730+if (leftOrder !== rightOrder) {
1731+return leftOrder - rightOrder;
1732+}
1733+if (leftBase !== rightBase) {
1734+return leftBase.localeCompare(rightBase);
1735+}
1736+return leftPath.localeCompare(rightPath);
1737+}
1738+1739+function normalizeCodexContextFilePath(filePath: string): string {
1740+return filePath.trim().replaceAll("\\", "/").toLowerCase();
1741+}
1742+1743+function getCodexContextFileBasename(filePath: string): string {
1744+return normalizeCodexContextFilePath(filePath).split("/").pop() ?? "";
1745+}
1746+16001747async function mirrorTranscriptBestEffort(params: {
16011748params: EmbeddedRunAttemptParams;
16021749agentId?: string;
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。