Add Codex happy path prompt snapshots (#75807)

* Add Codex prompt snapshots

* Fix prompt snapshot scenario catalogs

* Harden prompt snapshot drift check

* Fix CLI compat build export

* fix: keep codex snapshots out of core plugin surface

* fix: harden prompt snapshot ci checks

* fix: accept readonly web search onboarding scopes

* fix: repair plugin sdk package boundary types

* fix: clear prompt snapshot ci regressions

* fix: clear latest main ci checks

* fix: resolve latest main discord helper overlap

* fix: refresh codex dynamic tool snapshots

* fix: align prompt snapshot branch with latest ci

* fix: isolate plugin auto enable tests

* test: refresh prompt dynamic tool snapshots

* fix: stabilize bundled channel auto enable

* fix: clean stale prompt snapshots
This commit is contained in:
pashpashpash
2026-05-02 08:59:55 -07:00
committed by GitHub
parent 4fb520d9b7
commit 563dca82f4
46 changed files with 7920 additions and 133 deletions

View File

@@ -0,0 +1,31 @@
import type { CodexPluginConfig } from "./config.js";
export const CODEX_NATIVE_FIRST_DYNAMIC_TOOL_EXCLUDES = [
"read",
"write",
"edit",
"apply_patch",
"exec",
"process",
"update_plan",
] as const;
export function applyCodexDynamicToolProfile<T extends { name: string }>(
tools: T[],
config: Pick<CodexPluginConfig, "codexDynamicToolsProfile" | "codexDynamicToolsExclude">,
): T[] {
const excludes = new Set<string>();
const profile = config.codexDynamicToolsProfile ?? "native-first";
if (profile === "native-first") {
for (const name of CODEX_NATIVE_FIRST_DYNAMIC_TOOL_EXCLUDES) {
excludes.add(name);
}
}
for (const name of config.codexDynamicToolsExclude ?? []) {
const trimmed = name.trim();
if (trimmed) {
excludes.add(trimmed);
}
}
return excludes.size === 0 ? tools : tools.filter((tool) => !excludes.has(tool.name));
}

View File

@@ -54,6 +54,7 @@ import {
type CodexPluginConfig,
} from "./config.js";
import { projectContextEngineAssemblyForCodex } from "./context-engine-projection.js";
import { applyCodexDynamicToolProfile } from "./dynamic-tool-profile.js";
import { createCodexDynamicToolBridge, type CodexDynamicToolBridge } from "./dynamic-tools.js";
import { handleCodexAppServerElicitationRequest } from "./elicitation-bridge.js";
import { CodexAppServerEventProjector } from "./event-projector.js";
@@ -99,15 +100,6 @@ const CODEX_APP_SERVER_STARTUP_CONNECTION_CLOSE_MAX_ATTEMPTS = 3;
const CODEX_TURN_COMPLETION_IDLE_TIMEOUT_MS = 60_000;
const CODEX_TURN_TERMINAL_IDLE_TIMEOUT_MS = 30 * 60_000;
const CODEX_STEER_ALL_DEBOUNCE_MS = 500;
const CODEX_NATIVE_FIRST_DYNAMIC_TOOL_EXCLUDES = [
"read",
"write",
"edit",
"apply_patch",
"exec",
"process",
"update_plan",
] as const;
const LOG_FIELD_MAX_LENGTH = 160;
type OpenClawCodingToolsOptions = NonNullable<
@@ -1499,26 +1491,6 @@ async function buildDynamicTools(input: DynamicToolBuildParams) {
});
}
function applyCodexDynamicToolProfile<T extends { name: string }>(
tools: T[],
config: CodexPluginConfig,
): T[] {
const excludes = new Set<string>();
const profile = config.codexDynamicToolsProfile ?? "native-first";
if (profile === "native-first") {
for (const name of CODEX_NATIVE_FIRST_DYNAMIC_TOOL_EXCLUDES) {
excludes.add(name);
}
}
for (const name of config.codexDynamicToolsExclude ?? []) {
const trimmed = name.trim();
if (trimmed) {
excludes.add(trimmed);
}
}
return excludes.size === 0 ? tools : tools.filter((tool) => !excludes.has(tool.name));
}
async function withCodexStartupTimeout<T>(params: {
timeoutMs: number;
timeoutFloorMs?: number;

View File

@@ -97,25 +97,19 @@ export async function startOrResumeThread(params: {
}
}
const modelProvider = resolveCodexAppServerModelProvider(params.params.provider);
const response = assertCodexThreadStartResponse(
await params.client.request("thread/start", {
model: params.params.modelId,
...(modelProvider ? { modelProvider } : {}),
cwd: params.cwd,
approvalPolicy: params.appServer.approvalPolicy,
approvalsReviewer: params.appServer.approvalsReviewer,
sandbox: params.appServer.sandbox,
...(params.appServer.serviceTier ? { serviceTier: params.appServer.serviceTier } : {}),
serviceName: "OpenClaw",
...(params.config ? { config: params.config } : {}),
developerInstructions:
params.developerInstructions ?? buildDeveloperInstructions(params.params),
dynamicTools: params.dynamicTools,
experimentalRawEvents: true,
persistExtendedHistory: true,
} satisfies CodexThreadStartParams),
await params.client.request(
"thread/start",
buildThreadStartParams(params.params, {
cwd: params.cwd,
dynamicTools: params.dynamicTools,
appServer: params.appServer,
developerInstructions: params.developerInstructions,
config: params.config,
}),
),
);
const modelProvider = resolveCodexAppServerModelProvider(params.params.provider);
const createdAt = new Date().toISOString();
await writeCodexAppServerBinding(params.params.sessionFile, {
threadId: response.thread.id,
@@ -140,6 +134,34 @@ export async function startOrResumeThread(params: {
};
}
export function buildThreadStartParams(
params: EmbeddedRunAttemptParams,
options: {
cwd: string;
dynamicTools: CodexDynamicToolSpec[];
appServer: CodexAppServerRuntimeOptions;
developerInstructions?: string;
config?: JsonObject;
},
): CodexThreadStartParams {
const modelProvider = resolveCodexAppServerModelProvider(params.provider);
return {
model: params.modelId,
...(modelProvider ? { modelProvider } : {}),
cwd: options.cwd,
approvalPolicy: options.appServer.approvalPolicy,
approvalsReviewer: options.appServer.approvalsReviewer,
sandbox: options.appServer.sandbox,
...(options.appServer.serviceTier ? { serviceTier: options.appServer.serviceTier } : {}),
serviceName: "OpenClaw",
...(options.config ? { config: options.config } : {}),
developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params),
dynamicTools: options.dynamicTools,
experimentalRawEvents: true,
persistExtendedHistory: true,
};
}
export function buildThreadResumeParams(
params: EmbeddedRunAttemptParams,
options: {

View File

@@ -0,0 +1,79 @@
import type {
AnyAgentTool,
EmbeddedRunAttemptParams,
} from "openclaw/plugin-sdk/agent-harness-runtime";
import {
type CodexAppServerRuntimeOptions,
resolveCodexAppServerRuntimeOptions,
} from "./src/app-server/config.js";
import type { CodexPluginConfig } from "./src/app-server/config.js";
import { applyCodexDynamicToolProfile } from "./src/app-server/dynamic-tool-profile.js";
import { createCodexDynamicToolBridge } from "./src/app-server/dynamic-tools.js";
import type { CodexDynamicToolSpec, JsonObject } from "./src/app-server/protocol.js";
import {
buildDeveloperInstructions,
buildThreadResumeParams,
buildThreadStartParams,
buildTurnStartParams,
} from "./src/app-server/thread-lifecycle.js";
type CodexHarnessPromptSnapshot = {
developerInstructions: string;
threadStartParams: ReturnType<typeof buildThreadStartParams>;
threadResumeParams: ReturnType<typeof buildThreadResumeParams>;
turnStartParams: ReturnType<typeof buildTurnStartParams>;
};
export function resolveCodexPromptSnapshotAppServerOptions(
pluginConfig?: unknown,
): CodexAppServerRuntimeOptions {
return resolveCodexAppServerRuntimeOptions({
pluginConfig,
env: {},
});
}
export function buildCodexHarnessPromptSnapshot(params: {
attempt: EmbeddedRunAttemptParams;
cwd: string;
threadId: string;
dynamicTools: CodexDynamicToolSpec[];
appServer: CodexAppServerRuntimeOptions;
config?: JsonObject;
promptText?: string;
}): CodexHarnessPromptSnapshot {
const developerInstructions = buildDeveloperInstructions(params.attempt);
return {
developerInstructions,
threadStartParams: buildThreadStartParams(params.attempt, {
cwd: params.cwd,
dynamicTools: params.dynamicTools,
appServer: params.appServer,
developerInstructions,
config: params.config,
}),
threadResumeParams: buildThreadResumeParams(params.attempt, {
threadId: params.threadId,
appServer: params.appServer,
developerInstructions,
config: params.config,
}),
turnStartParams: buildTurnStartParams(params.attempt, {
threadId: params.threadId,
cwd: params.cwd,
appServer: params.appServer,
promptText: params.promptText,
}),
};
}
export function createCodexDynamicToolSpecsForPromptSnapshot(params: {
tools: AnyAgentTool[];
pluginConfig?: Pick<CodexPluginConfig, "codexDynamicToolsProfile" | "codexDynamicToolsExclude">;
}): CodexDynamicToolSpec[] {
const profiledTools = applyCodexDynamicToolProfile(params.tools, params.pluginConfig ?? {});
return createCodexDynamicToolBridge({
tools: profiledTools,
signal: new AbortController().signal,
}).specs;
}

View File

@@ -54,6 +54,7 @@ type GeminiInlinePart = {
inlineData: { mimeType: string; data: string };
};
type GeminiPart = GeminiTextPart | GeminiInlinePart;
type GeminiEmbeddingInputPart = NonNullable<EmbeddingInput["parts"]>[number];
type GeminiEmbeddingRequest = {
content: { parts: GeminiPart[] };
taskType: GeminiTaskType;
@@ -85,7 +86,7 @@ export function buildGeminiEmbeddingRequest(params: {
}): GeminiEmbeddingRequest {
const request: GeminiEmbeddingRequest = {
content: {
parts: params.input.parts?.map((part) =>
parts: params.input.parts?.map((part: GeminiEmbeddingInputPart) =>
part.type === "text"
? ({ text: part.text } satisfies GeminiTextPart)
: ({