Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled

Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11),
free to diverge. Tree copied sans upstream .git; upstream remote added for
future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19.
Preserves docs/ARCHITECTURE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
2026-07-05 09:36:54 +00:00
parent 3216769225
commit bedb527145
21108 changed files with 6010766 additions and 0 deletions

View File

@@ -0,0 +1,958 @@
// Happy path prompt snapshot helper reads expected prompt snapshot files.
import fs from "node:fs";
import path from "node:path";
import type { Model } from "openclaw/plugin-sdk/llm";
import { resolveHeartbeatPromptForResponseTool } from "../../../src/auto-reply/heartbeat.js";
import {
buildDirectChatContext,
buildGroupChatContext,
buildGroupIntro,
} from "../../../src/auto-reply/reply/groups.js";
import {
buildInboundMetaSystemPrompt,
buildInboundUserContextPrefix,
} from "../../../src/auto-reply/reply/inbound-meta.js";
import { buildReplyPromptBodies } from "../../../src/auto-reply/reply/prompt-prelude.js";
import type { TemplateContext } from "../../../src/auto-reply/templating.js";
import { SILENT_REPLY_TOKEN } from "../../../src/auto-reply/tokens.js";
import { normalizeChatType } from "../../../src/channels/chat-type.js";
import type { OpenClawConfig } from "../../../src/config/types.openclaw.js";
import type {
AnyAgentTool,
EmbeddedRunAttemptParams,
} from "../../../src/plugin-sdk/agent-harness-runtime.js";
import { normalizeAgentRuntimeTools } from "../../../src/plugin-sdk/agent-harness-runtime.js";
import { createOpenClawCodingTools } from "../../../src/plugin-sdk/agent-harness.js";
import { resolveRelativeBundledPluginPublicModuleId } from "../../../src/test-utils/bundled-plugin-public-surface.js";
import {
CODEX_MODEL_PROMPT_FIXTURE_DIR,
CODEX_RUNTIME_HAPPY_PATH_PROMPT_SNAPSHOT_DIR,
} from "./prompt-snapshot-paths.js";
// Builds Codex happy-path prompt snapshot fixtures for agent prompt regression tests.
export { CODEX_MODEL_PROMPT_FIXTURE_DIR, CODEX_RUNTIME_HAPPY_PATH_PROMPT_SNAPSHOT_DIR };
const WORKSPACE_DIR = "/tmp/openclaw-happy-path/workspace";
const AGENT_DIR = "/tmp/openclaw-happy-path/agent";
const SESSION_FILE = "/tmp/openclaw-happy-path/session.jsonl";
const MODEL_ID = "gpt-5.5";
const CODEX_PROMPT_PERSONALITY = "pragmatic";
const CODEX_MODEL_PROMPT_FIXTURE_PATH = path.join(
CODEX_MODEL_PROMPT_FIXTURE_DIR,
`${MODEL_ID}.${CODEX_PROMPT_PERSONALITY}.instructions.md`,
);
const CODEX_MODEL_PROMPT_SOURCE_PATH = path.join(
CODEX_MODEL_PROMPT_FIXTURE_DIR,
`${MODEL_ID}.${CODEX_PROMPT_PERSONALITY}.source.json`,
);
const CODEX_YOLO_PERMISSION_INSTRUCTIONS = [
"Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `danger-full-access`: No filesystem sandboxing - all commands are permitted. Network access is enabled.",
"Approval policy is currently never. Do not provide the `sandbox_permissions` for any reason, commands will be rejected.",
].join("\n");
const HAPPY_PATH_TOOL_NAMES = new Set([
"nodes",
"cron",
"message",
"heartbeat_respond",
"tts",
"gateway",
"agents_list",
"sessions_list",
"sessions_history",
"sessions_send",
"sessions_spawn",
"sessions_yield",
"subagents",
"session_status",
"web_search",
"web_fetch",
]);
type CodexDynamicToolFunctionSpec = {
type?: "function";
name: string;
description?: string;
inputSchema?: unknown;
};
type CodexDynamicToolNamespaceSpec = {
type: "namespace";
name: string;
tools: CodexDynamicToolFunctionSpec[];
};
type CodexDynamicToolSpec = CodexDynamicToolFunctionSpec | CodexDynamicToolNamespaceSpec;
function flattenCodexDynamicToolSpecs(
specs: readonly CodexDynamicToolSpec[],
): CodexDynamicToolFunctionSpec[] {
return specs.flatMap((spec) => (spec.type === "namespace" ? spec.tools : [spec]));
}
type CodexPromptSnapshotApi = {
resolveCodexPromptSnapshotAppServerOptions: (pluginConfig?: unknown) => unknown;
buildCodexHarnessPromptSnapshot: (params: {
attempt: EmbeddedRunAttemptParams;
cwd: string;
threadId: string;
dynamicTools: CodexDynamicToolSpec[];
appServer: unknown;
config?: Record<string, unknown>;
promptText?: string;
developerInstructionAdditions?: string;
turnScopedDeveloperInstructions?: string;
heartbeatCollaborationInstructions?: string;
}) => {
developerInstructions: string;
threadStartParams: Record<string, unknown>;
threadResumeParams: Record<string, unknown>;
turnStartParams: Record<string, unknown>;
};
createCodexDynamicToolSpecsForPromptSnapshot: (params: {
tools: AnyAgentTool[];
pluginConfig?: {
codexDynamicToolsLoading?: "searchable" | "direct";
codexDynamicToolsExclude?: string[];
};
directToolNames?: string[];
}) => CodexDynamicToolSpec[];
};
type PromptSnapshotFile = {
path: string;
content: string;
};
type PromptScenario = {
id: string;
title: string;
notes: string[];
trigger: "user" | "heartbeat";
ctx: TemplateContext;
prompt: string;
extraSystemPrompt: string;
dynamicTools: CodexDynamicToolSpec[];
toolSnapshotFile: string;
};
const CODEX_TEST_API_MODULE_ID = resolveRelativeBundledPluginPublicModuleId({
fromModuleUrl: import.meta.url,
pluginId: "codex",
artifactBasename: "test-api.js",
});
/** Load the Codex public test API without hardcoding plugin-private paths. */
async function loadCodexPromptSnapshotApi(): Promise<CodexPromptSnapshotApi> {
return (await import(CODEX_TEST_API_MODULE_ID)) as CodexPromptSnapshotApi;
}
const CODEX_WORKSPACE_BOOTSTRAP_CONTEXT_FILES = [
{
path: path.join(WORKSPACE_DIR, "MEMORY.md"),
content: "<MEMORY.md contents will be here>",
},
] as const;
const CODEX_WORKSPACE_THREAD_DEVELOPER_CONTEXT_FILES = [
{
path: path.join(WORKSPACE_DIR, "TOOLS.md"),
content: "<TOOLS.md contents will be here>",
},
] as const;
const CODEX_WORKSPACE_TURN_SCOPED_DEVELOPER_CONTEXT_FILES = [
{
path: path.join(WORKSPACE_DIR, "IDENTITY.md"),
content: "<IDENTITY.md contents will be here>",
},
{
path: path.join(WORKSPACE_DIR, "SOUL.md"),
content: "<SOUL.md contents will be here>",
},
{
path: path.join(WORKSPACE_DIR, "USER.md"),
content: "<USER.md contents will be here>",
},
] as const;
const CODEX_HEARTBEAT_CONTEXT_FILE = {
path: path.join(WORKSPACE_DIR, "HEARTBEAT.md"),
content: "<HEARTBEAT.md contents will be here>",
} as const;
const CODEX_WORKSPACE_BOOTSTRAP_PROMPT_CONTEXT = [
"OpenClaw loaded these user-editable workspace files for the current turn. Codex loads AGENTS.md natively. TOOLS.md is provided as inherited Codex developer instructions. SOUL.md, IDENTITY.md, and USER.md are provided as turn-scoped collaboration instructions so native Codex subagents do not inherit them. HEARTBEAT.md is handled by heartbeat collaboration-mode guidance. Those files are not repeated here.",
"",
"# Project Context",
"",
"The following project context files have been loaded:",
"",
...CODEX_WORKSPACE_BOOTSTRAP_CONTEXT_FILES.flatMap((file) => [
`## ${file.path}`,
"",
file.content,
"",
]),
]
.join("\n")
.trim();
const CODEX_WORKSPACE_THREAD_DEVELOPER_INSTRUCTIONS = [
"## OpenClaw Workspace Instructions",
"",
"OpenClaw loaded these workspace instruction files from the active agent workspace. Internalize and follow them accordingly.",
"",
...CODEX_WORKSPACE_THREAD_DEVELOPER_CONTEXT_FILES.flatMap((file) => [
`### ${file.path}`,
"",
file.content,
"",
]),
]
.join("\n")
.trim();
const CODEX_WORKSPACE_TURN_SCOPED_DEVELOPER_INSTRUCTIONS = [
"## OpenClaw Agent Soul",
"",
"OpenClaw loaded these workspace instruction files from the active agent workspace. They are the canonical definitions of who you are, how you think and work, and the human you work alongside. Internalize and follow them accordingly.",
"",
...CODEX_WORKSPACE_TURN_SCOPED_DEVELOPER_CONTEXT_FILES.flatMap((file) => [
`### ${file.path}`,
"",
file.content,
"",
]),
]
.join("\n")
.trim();
const CODEX_HEARTBEAT_COLLABORATION_INSTRUCTIONS = [
"## OpenClaw Heartbeat Workspace",
"",
"HEARTBEAT.md exists in the active agent workspace. Read it before proceeding with this heartbeat, then decide what action is appropriate.",
"",
`- ${CODEX_HEARTBEAT_CONTEXT_FILE.path}`,
].join("\n");
const CODEX_PROMPT_SNAPSHOT_THREAD_CONFIG = {
"features.code_mode_only": false,
};
const baseConfig: OpenClawConfig = {
messages: {
groupChat: {
visibleReplies: "message_tool",
},
},
agents: {
defaults: {
heartbeat: {
enabled: true,
every: "30m",
},
},
},
tools: {
profiles: {
coding: {
allow: [
"message",
"heartbeat_respond",
"sessions_spawn",
"sessions_list",
"sessions_yield",
"cron",
"memory_search",
"memory_get",
"session_status",
],
},
},
},
};
const dynamicToolsConfig: OpenClawConfig = {
...baseConfig,
plugins: {
enabled: true,
slots: {
memory: "none",
},
},
};
const happyPathModel = {
id: MODEL_ID,
provider: "openai",
api: "responses",
input: ["text"],
contextWindow: 272_000,
} as unknown as Model;
function stableJsonValue(value: unknown): unknown {
if (Array.isArray(value)) {
return value.map(stableJsonValue);
}
if (!value || typeof value !== "object") {
return value;
}
return Object.fromEntries(
Object.entries(value)
.filter(([, child]) => child !== undefined)
.toSorted(([left], [right]) => left.localeCompare(right))
.map(([key, child]) => [key, stableJsonValue(child)]),
);
}
function stableJson(value: unknown): string {
return `${JSON.stringify(stableJsonValue(value), null, 2)}\n`;
}
function markdownFence(info: string, value: string): string {
const body = value.trimEnd();
const longestBacktickRun = Math.max(
3,
...(body.match(/`+/g) ?? []).map((match) => match.length + 1),
);
const fence = "`".repeat(longestBacktickRun);
return [`${fence}${info}`, body, fence].join("\n");
}
function readFixture(pathFromRepoRoot: string): string {
return fs.readFileSync(path.resolve(pathFromRepoRoot), "utf8");
}
function approximateTokens(value: string): number {
return Math.ceil(value.length / 4);
}
function textStats(value: string): { chars: number; roughTokens: number } {
return {
chars: value.length,
roughTokens: approximateTokens(value),
};
}
function createPrompt(ctx: TemplateContext, body: string): string {
const inboundUserContext = buildInboundUserContextPrefix(ctx);
return buildReplyPromptBodies({
ctx,
sessionCtx: ctx,
effectiveBaseBody: [inboundUserContext, body].filter(Boolean).join("\n\n"),
prefixedBody: [inboundUserContext, body].filter(Boolean).join("\n\n"),
}).prefixedCommandBody;
}
function createExtraSystemPrompt(params: {
ctx: TemplateContext;
chatContext: string;
intro?: string;
}): string {
return [
buildInboundMetaSystemPrompt(params.ctx),
params.chatContext,
params.intro,
params.ctx.GroupSystemPrompt,
]
.filter(Boolean)
.join("\n\n");
}
function createAttempt(params: {
scenario: PromptScenario;
sessionKey: string;
}): EmbeddedRunAttemptParams {
return {
agentId: "main",
agentDir: AGENT_DIR,
workspaceDir: WORKSPACE_DIR,
sessionFile: SESSION_FILE,
sessionKey: params.sessionKey,
sessionId: `session-${params.scenario.id}`,
runId: `run-${params.scenario.id}`,
provider: "codex",
modelId: MODEL_ID,
model: happyPathModel,
prompt: params.scenario.prompt,
extraSystemPrompt: params.scenario.extraSystemPrompt,
config: baseConfig,
thinkLevel: "medium",
timeoutMs: 600_000,
trigger: params.scenario.trigger,
messageProvider: params.scenario.ctx.Provider,
messageChannel: params.scenario.ctx.OriginatingChannel,
chatType: normalizeChatType(params.scenario.ctx.ChatType),
agentAccountId: params.scenario.ctx.AccountId,
messageTo: params.scenario.ctx.OriginatingTo,
messageThreadId: params.scenario.ctx.MessageThreadId,
groupId: params.scenario.ctx.From,
groupChannel: params.scenario.ctx.GroupChannel,
groupSpace: params.scenario.ctx.GroupSpace,
senderId: params.scenario.ctx.SenderId,
senderName: params.scenario.ctx.SenderName,
senderUsername: params.scenario.ctx.SenderUsername,
senderE164: params.scenario.ctx.SenderE164,
senderIsOwner: true,
currentMessageId: params.scenario.ctx.MessageSid,
sourceReplyDeliveryMode: "message_tool_only",
forceMessageTool: true,
authStorage: {} as EmbeddedRunAttemptParams["authStorage"],
modelRegistry: {} as EmbeddedRunAttemptParams["modelRegistry"],
} as EmbeddedRunAttemptParams;
}
function createDynamicTools(params: {
codexApi: CodexPromptSnapshotApi;
ctx: TemplateContext;
trigger: "user" | "heartbeat";
}): CodexDynamicToolSpec[] {
const tools = createOpenClawCodingTools({
agentId: "main",
workspaceDir: WORKSPACE_DIR,
agentDir: AGENT_DIR,
sessionKey: params.ctx.SessionKey,
sessionId: `session-tools-${params.trigger}`,
runId: `run-tools-${params.trigger}`,
messageProvider: params.ctx.Provider,
agentAccountId: params.ctx.AccountId,
messageTo: params.ctx.OriginatingTo,
messageThreadId: params.ctx.MessageThreadId,
groupId: params.ctx.From,
groupChannel: params.ctx.GroupChannel,
groupSpace: params.ctx.GroupSpace,
senderId: params.ctx.SenderId,
senderName: params.ctx.SenderName,
senderUsername: params.ctx.SenderUsername,
senderE164: params.ctx.SenderE164,
senderIsOwner: true,
currentMessageId: params.ctx.MessageSid,
modelProvider: "openai",
modelId: MODEL_ID,
modelApi: "responses",
modelContextWindowTokens: 272_000,
forceMessageTool: true,
enableHeartbeatTool: params.trigger === "heartbeat",
forceHeartbeatTool: params.trigger === "heartbeat",
trigger: params.trigger,
config: dynamicToolsConfig,
toolConstructionPlan: {
includeBaseCodingTools: false,
includeShellTools: false,
includeChannelTools: false,
includeOpenClawTools: true,
includePluginTools: false,
},
});
const normalized = normalizeAgentRuntimeTools({
tools,
runtimePlan: undefined,
provider: "codex",
config: dynamicToolsConfig,
workspaceDir: WORKSPACE_DIR,
env: {},
modelId: MODEL_ID,
modelApi: "responses",
model: happyPathModel,
});
return params.codexApi.createCodexDynamicToolSpecsForPromptSnapshot({
tools: normalized.filter((tool) => HAPPY_PATH_TOOL_NAMES.has(tool.name)),
directToolNames: ["message"],
});
}
function createScenarios(codexApi: CodexPromptSnapshotApi): PromptScenario[] {
const telegramDirectCtx: TemplateContext = {
Provider: "telegram",
Surface: "telegram",
OriginatingChannel: "telegram",
OriginatingTo: "user:1000001",
AccountId: "primary",
ChatType: "direct",
SessionKey: "agent:main:telegram:direct:1000001",
MessageSid: "tg-msg-0001",
SenderId: "1000001",
SenderName: "Pash",
SenderUsername: "pash",
Body: "Can you check whether the nightly build finished and tell me what happened?",
BodyStripped: "Can you check whether the nightly build finished and tell me what happened?",
};
const discordGroupCtx: TemplateContext = {
Provider: "discord",
Surface: "discord",
OriginatingChannel: "discord",
OriginatingTo: "channel:987654321",
From: "guild:123456789/channel:987654321",
AccountId: "primary",
ChatType: "group",
SessionKey: "agent:main:discord:guild:123456789:channel:987654321",
MessageSid: "discord-msg-0001",
SenderId: "424242",
SenderName: "Pash",
SenderUsername: "pash",
GroupSubject: "OpenClaw maintainers",
GroupChannel: "#agent-sandbox",
GroupSpace: "OpenClaw",
ConversationLabel: "OpenClaw/#agent-sandbox",
WasMentioned: true,
InboundHistory: [
{
sender: "Peter",
body: "I pushed the Discord-side message-tool bridge.",
},
{
sender: "Pash",
body: "@OpenClaw please verify the Codex happy path too.",
},
],
Body: "@OpenClaw can you audit whether this prompt path has conflicting silence instructions?",
BodyStripped: "can you audit whether this prompt path has conflicting silence instructions?",
};
const heartbeatCtx: TemplateContext = {
...telegramDirectCtx,
MessageSid: "heartbeat-0001",
Body: resolveHeartbeatPromptForResponseTool(),
BodyStripped: resolveHeartbeatPromptForResponseTool(),
};
const telegramDirectTools = createDynamicTools({
codexApi,
ctx: telegramDirectCtx,
trigger: "user",
});
const discordGroupTools = createDynamicTools({
codexApi,
ctx: discordGroupCtx,
trigger: "user",
});
const heartbeatTools = createDynamicTools({ codexApi, ctx: heartbeatCtx, trigger: "heartbeat" });
return [
{
id: "telegram-direct-codex-message-tool",
title: "Telegram Direct Codex Message Tool Turn",
notes: [
"Default happy path: OpenAI model through the Codex harness/runtime, Telegram direct conversation, and message-tool-only visible replies.",
"A quiet turn is represented by not calling `message(action=send)`; the normal final assistant text is private to OpenClaw/Codex.",
],
trigger: "user",
ctx: telegramDirectCtx,
prompt: createPrompt(
telegramDirectCtx,
telegramDirectCtx.BodyStripped ?? telegramDirectCtx.Body ?? "",
),
extraSystemPrompt: createExtraSystemPrompt({
ctx: telegramDirectCtx,
chatContext: buildDirectChatContext({
sessionCtx: telegramDirectCtx,
sourceReplyDeliveryMode: "message_tool_only",
}),
}),
dynamicTools: telegramDirectTools,
toolSnapshotFile: "codex-dynamic-tools.telegram-direct.json",
},
{
id: "discord-group-codex-message-tool",
title: "Discord Group Codex Message Tool Turn",
notes: [
"Default happy path: the same Codex agent is mentioned in a Discord group/channel while Telegram can remain the user's primary direct interface.",
"Group-visible output must be explicit through the message tool; the model is also told to mostly lurk unless directly addressed or clearly useful.",
],
trigger: "user",
ctx: discordGroupCtx,
prompt: createPrompt(
discordGroupCtx,
discordGroupCtx.BodyStripped ?? discordGroupCtx.Body ?? "",
),
extraSystemPrompt: createExtraSystemPrompt({
ctx: discordGroupCtx,
chatContext: buildGroupChatContext({
sessionCtx: discordGroupCtx,
sourceReplyDeliveryMode: "message_tool_only",
silentReplyPolicy: "allow",
silentToken: SILENT_REPLY_TOKEN,
}),
intro: buildGroupIntro({
defaultActivation: "mention",
}),
}),
dynamicTools: discordGroupTools,
toolSnapshotFile: "codex-dynamic-tools.discord-group.json",
},
{
id: "telegram-heartbeat-codex-tool",
title: "Telegram Direct Codex Heartbeat Tool Turn",
notes: [
"Heartbeat happy path: Codex receives the structured `heartbeat_respond` dynamic tool in the searchable catalog instead of the initial tool context.",
"The heartbeat tool still carries the notify/no-notify decision, outcome, summary, and optional notification text instead of relying only on final-text parsing.",
],
trigger: "heartbeat",
ctx: heartbeatCtx,
prompt: createPrompt(heartbeatCtx, heartbeatCtx.BodyStripped ?? heartbeatCtx.Body ?? ""),
extraSystemPrompt: createExtraSystemPrompt({
ctx: heartbeatCtx,
chatContext: buildDirectChatContext({
sessionCtx: heartbeatCtx,
sourceReplyDeliveryMode: "message_tool_only",
}),
}),
dynamicTools: heartbeatTools,
toolSnapshotFile: "codex-dynamic-tools.heartbeat-turn.json",
},
];
}
function selectedThreadStartParams(value: Record<string, unknown>): Record<string, unknown> {
return {
...value,
developerInstructions: "<see Reconstructed Model-Bound Prompt Layers>",
dynamicTools: Array.isArray(value.dynamicTools)
? flattenCodexDynamicToolSpecs(value.dynamicTools as CodexDynamicToolSpec[]).map(
(tool) => tool.name,
)
: value.dynamicTools,
};
}
function selectedThreadResumeParams(value: Record<string, unknown>): Record<string, unknown> {
return {
...value,
developerInstructions: "<see Reconstructed Model-Bound Prompt Layers>",
};
}
function selectedTurnStartParams(value: Record<string, unknown>): Record<string, unknown> {
return {
...value,
input: Array.isArray(value.input)
? value.input.map((item) =>
item && typeof item === "object" && "type" in item
? {
...item,
text:
typeof (item as { text?: unknown }).text === "string"
? "<see Reconstructed Model-Bound Prompt Layers>"
: (item as { text?: unknown }).text,
}
: item,
)
: value.input,
};
}
function renderModelBoundPromptLayers(params: {
scenario: PromptScenario;
codexSnapshot: ReturnType<CodexPromptSnapshotApi["buildCodexHarnessPromptSnapshot"]>;
dynamicToolsJson: string;
}): string[] {
const codexModelInstructions = readFixture(CODEX_MODEL_PROMPT_FIXTURE_PATH);
const codexModelSource = JSON.parse(readFixture(CODEX_MODEL_PROMPT_SOURCE_PATH)) as unknown;
const codexConfigInstructions =
typeof params.codexSnapshot.threadStartParams.config === "object" &&
params.codexSnapshot.threadStartParams.config &&
"instructions" in params.codexSnapshot.threadStartParams.config &&
typeof params.codexSnapshot.threadStartParams.config.instructions === "string"
? params.codexSnapshot.threadStartParams.config.instructions
: "";
const openClawDeveloperInstructions = params.codexSnapshot.developerInstructions;
const codexCollaborationModeInstructions =
typeof params.codexSnapshot.turnStartParams.collaborationMode?.settings
?.developer_instructions === "string"
? params.codexSnapshot.turnStartParams.collaborationMode.settings.developer_instructions
: "";
const turnInputText = readCodexTurnInputText(params.codexSnapshot.turnStartParams);
const textOnlyTotal = [
codexModelInstructions,
CODEX_YOLO_PERMISSION_INSTRUCTIONS,
codexConfigInstructions,
openClawDeveloperInstructions,
codexCollaborationModeInstructions,
turnInputText,
]
.filter(Boolean)
.join("\n\n");
const totalWithDynamicToolJson = [textOnlyTotal, params.dynamicToolsJson].join("\n\n");
return [
"## Reconstructed Model-Bound Prompt Layers",
"",
"This is the deterministic model-bound layer stack OpenClaw can snapshot for the Codex happy path. It uses a pinned Codex `gpt-5.5` prompt fixture generated from Codex's model catalog/cache shape, then adds the Codex permission developer text, Codex thread config instructions when present, OpenClaw developer instructions, turn-scoped collaboration-mode instructions when OpenClaw provides them, turn input with OpenClaw runtime context, and the OpenClaw dynamic tool catalog. Codex can still add runtime-owned context such as native workspace `AGENTS.md`, environment context, memories, app/plugin instructions, and built-in collaboration-mode instructions inside the Codex runtime.",
"",
"### Layer Metadata",
"",
markdownFence(
"json",
stableJson({
codexModelInstructions: {
fixture: CODEX_MODEL_PROMPT_FIXTURE_PATH,
source: codexModelSource,
},
codexPermissions: {
sandbox: "danger-full-access",
approvalPolicy: "never",
networkAccess: "enabled",
},
openClawRuntime: {
configInstructionsFrom: "extensions/codex app-server thread/start config.instructions",
workspaceBootstrapContextFrom:
"extensions/codex app-server turn/start input OpenClaw runtime context",
developerInstructionsFrom:
"extensions/codex app-server thread/start developerInstructions",
collaborationModeDeveloperInstructionsFrom:
"extensions/codex app-server turn/start collaborationMode.settings.developer_instructions",
userInputFrom: "extensions/codex app-server turn/start input",
dynamicToolsFrom: params.scenario.toolSnapshotFile,
},
limitations: [
"This is a reconstructed prompt-layer snapshot, not a byte-for-byte raw OpenAI request captured from Codex core.",
"Codex-owned workspace AGENTS.md, environment context, memories, app/plugin instructions, built-in Default collaboration-mode instructions, and provider tool serialization are still runtime-owned gaps until Codex exposes a rendered-prompt inspection API.",
],
}),
),
"",
"### Rough Text Token Estimates",
"",
markdownFence(
"json",
stableJson({
codexModelInstructions: textStats(codexModelInstructions),
codexPermissionDeveloperInstructions: textStats(CODEX_YOLO_PERMISSION_INSTRUCTIONS),
codexWorkspaceBootstrapConfigInstructions: textStats(codexConfigInstructions),
openClawDeveloperInstructions: textStats(openClawDeveloperInstructions),
codexCollaborationModeDeveloperInstructions: textStats(codexCollaborationModeInstructions),
userInputText: textStats(turnInputText),
dynamicToolsJson: textStats(params.dynamicToolsJson),
totalTextOnly: textStats(textOnlyTotal),
totalWithDynamicToolsJson: textStats(totalWithDynamicToolJson),
}),
),
"",
`### System: Codex Model Instructions (${MODEL_ID}, ${CODEX_PROMPT_PERSONALITY})`,
"",
markdownFence("text", codexModelInstructions),
"",
"### Developer: Codex Permission Instructions",
"",
markdownFence("text", CODEX_YOLO_PERMISSION_INSTRUCTIONS),
"",
"### User: Codex Config Instructions",
"",
markdownFence("text", codexConfigInstructions),
"",
"### Developer: OpenClaw Runtime Instructions",
"",
markdownFence("text", openClawDeveloperInstructions),
"",
"### Developer: Codex Collaboration Mode Instructions",
"",
codexCollaborationModeInstructions
? markdownFence("text", codexCollaborationModeInstructions)
: "This turn asks Codex app-server to resolve its built-in Default collaboration-mode instructions at runtime.",
"",
"### User: Turn Input Text",
"",
markdownFence("text", turnInputText),
"",
"### Tools: Dynamic Tool Catalog",
"",
`Full JSON: \`${params.scenario.toolSnapshotFile}\``,
"",
];
}
function readCodexTurnInputText(turnStartParams: { input?: unknown }): string {
const input = turnStartParams.input;
if (!Array.isArray(input)) {
return "";
}
const firstText = input.find(
(item): item is { text: string } =>
item !== null &&
typeof item === "object" &&
typeof (item as { text?: unknown }).text === "string",
);
return firstText?.text ?? "";
}
function buildCodexOpenClawRuntimeContext(): string {
return [
"OpenClaw runtime context for this turn:",
"Treat this OpenClaw-provided context as supporting project/user reference for the current request.",
"",
"## OpenClaw Workspace Context",
"",
CODEX_WORKSPACE_BOOTSTRAP_PROMPT_CONTEXT,
].join("\n");
}
function prependCodexOpenClawRuntimeContext(prompt: string): string {
return [buildCodexOpenClawRuntimeContext(), "", "Current user request:", prompt].join("\n");
}
function renderScenarioSnapshot(
codexApi: CodexPromptSnapshotApi,
scenario: PromptScenario,
): string {
const attempt = createAttempt({
scenario,
sessionKey: scenario.ctx.SessionKey ?? `agent:main:${scenario.id}`,
});
const appServer = codexApi.resolveCodexPromptSnapshotAppServerOptions();
const codexTurnPromptText = prependCodexOpenClawRuntimeContext(scenario.prompt);
const codexSnapshot = codexApi.buildCodexHarnessPromptSnapshot({
attempt,
cwd: WORKSPACE_DIR,
threadId: `thread-${scenario.id}`,
dynamicTools: scenario.dynamicTools,
appServer,
config: CODEX_PROMPT_SNAPSHOT_THREAD_CONFIG,
promptText: codexTurnPromptText,
developerInstructionAdditions: CODEX_WORKSPACE_THREAD_DEVELOPER_INSTRUCTIONS,
turnScopedDeveloperInstructions: CODEX_WORKSPACE_TURN_SCOPED_DEVELOPER_INSTRUCTIONS,
heartbeatCollaborationInstructions:
scenario.trigger === "heartbeat" ? CODEX_HEARTBEAT_COLLABORATION_INSTRUCTIONS : undefined,
});
const dynamicToolFunctions = flattenCodexDynamicToolSpecs(scenario.dynamicTools);
const criticalToolSpecs = dynamicToolFunctions.filter((tool) =>
["message", "heartbeat_respond"].includes(tool.name),
);
const dynamicToolsJson = stableJson(scenario.dynamicTools);
return [
`# ${scenario.title}`,
"",
"<!-- Generated by `pnpm prompt:snapshots:gen`. Do not edit by hand. -->",
"",
"## Scope",
"",
...scenario.notes.map((note) => `- ${note}`),
"- This captures the OpenClaw-owned Codex app-server inputs and reconstructs the stable Codex model/permission layers from committed Codex prompt fixtures.",
"- This also simulates Codex workspace bootstrap routing: `TOOLS.md` as inherited developer instructions, `SOUL.md`, `IDENTITY.md`, and `USER.md` as turn-scoped collaboration instructions, `MEMORY.md` in turn input, and `HEARTBEAT.md` as a heartbeat-only file pointer.",
"",
"## Scenario Metadata",
"",
markdownFence(
"json",
stableJson({
harness: "codex",
runtime: "codex_app_server",
modelProvider: "openai",
model: MODEL_ID,
sourceReplyDeliveryMode: "message_tool_only",
trigger: scenario.trigger,
channel: scenario.ctx.Provider,
chatType: scenario.ctx.ChatType,
toolSnapshot: scenario.toolSnapshotFile,
codexModelInstructionsFixture: CODEX_MODEL_PROMPT_FIXTURE_PATH,
simulatedWorkspaceBootstrapFiles: CODEX_WORKSPACE_BOOTSTRAP_CONTEXT_FILES.map(
(file) => file.path,
),
simulatedWorkspaceDeveloperInstructionFiles:
CODEX_WORKSPACE_THREAD_DEVELOPER_CONTEXT_FILES.map((file) => file.path),
simulatedWorkspaceTurnScopedDeveloperInstructionFiles:
CODEX_WORKSPACE_TURN_SCOPED_DEVELOPER_CONTEXT_FILES.map((file) => file.path),
simulatedHeartbeatWorkspaceFile: CODEX_HEARTBEAT_CONTEXT_FILE.path,
}),
),
"",
"## Effective OpenClaw Config",
"",
markdownFence("json", stableJson(baseConfig)),
"",
"## Thread Start Params",
"",
markdownFence("json", stableJson(selectedThreadStartParams(codexSnapshot.threadStartParams))),
"",
"## Thread Resume Params",
"",
markdownFence("json", stableJson(selectedThreadResumeParams(codexSnapshot.threadResumeParams))),
"",
"## Turn Start Params",
"",
markdownFence("json", stableJson(selectedTurnStartParams(codexSnapshot.turnStartParams))),
"",
...renderModelBoundPromptLayers({ scenario, codexSnapshot, dynamicToolsJson }),
"## Dynamic Tool Names",
"",
markdownFence("json", stableJson(dynamicToolFunctions.map((tool) => tool.name))),
"",
"## Critical Visible-Reply Tool Specs",
"",
markdownFence("json", stableJson(criticalToolSpecs)),
"",
].join("\n");
}
function renderReadme(scenarios: PromptScenario[]): string {
return [
"# Codex Happy Path Prompt Snapshots",
"",
"<!-- Generated by `pnpm prompt:snapshots:gen`. Do not edit by hand. -->",
"",
"These fixtures capture the default OpenAI/Codex happy path for prompt review:",
"",
"- OpenAI model through the Codex harness and Codex app-server runtime.",
"- Codex harness default coverage for tool-only visible source replies.",
"- Telegram direct chat, Discord group chat, and a heartbeat turn with `heartbeat_respond` available through searchable dynamic tools.",
"",
"The Markdown files show selected app-server thread/turn params plus a reconstructed model-bound prompt layer stack: Codex `gpt-5.5` model instructions from a pinned Codex model catalog fixture, Codex permission developer instructions for the happy-path yolo profile, OpenClaw developer instructions, turn input with simulated OpenClaw workspace bootstrap runtime context, heartbeat collaboration-mode guidance when applicable, and references to the complete dynamic tool catalog.",
"",
"The workspace bootstrap simulation includes dummy workspace contents so prompt reviewers can see how OpenClaw routes stable profile files into Codex developer instructions, keeps `MEMORY.md` in turn input, and points heartbeat turns at `HEARTBEAT.md` without inlining it. `AGENTS.md` is intentionally not repeated here because Codex loads it natively.",
"",
"The tool catalog is pinned to the canonical happy-path OpenClaw tools so optional locally installed plugin tools do not create fixture churn.",
"",
"The Codex model prompt fixture is generated from the same Codex model catalog/cache shape that the Codex runtime uses for remote model metadata. Regenerate it from Codex's runtime cache or, when present, a local Codex checkout with:",
"",
markdownFence("sh", "pnpm prompt:snapshots:sync-codex-model"),
"",
"These snapshots are still not a byte-for-byte raw OpenAI request capture. Codex-owned native `AGENTS.md`, environment context, memories, app/plugin instructions, and built-in collaboration-mode instructions can be added inside the Codex runtime after OpenClaw sends thread and turn params.",
"",
"Regenerate with:",
"",
markdownFence("sh", "pnpm prompt:snapshots:gen"),
"",
"Check for drift with:",
"",
markdownFence("sh", "pnpm prompt:snapshots:check"),
"",
"Snapshots:",
"",
...scenarios.map((scenario) => `- ${scenario.id}.md`),
...scenarios.map((scenario) => `- ${scenario.toolSnapshotFile}`),
"",
"Codex model prompt fixtures:",
"",
`- ${path.relative(
CODEX_RUNTIME_HAPPY_PATH_PROMPT_SNAPSHOT_DIR,
CODEX_MODEL_PROMPT_FIXTURE_PATH,
)}`,
`- ${path.relative(
CODEX_RUNTIME_HAPPY_PATH_PROMPT_SNAPSHOT_DIR,
CODEX_MODEL_PROMPT_SOURCE_PATH,
)}`,
"",
].join("\n");
}
/** Build all Codex happy-path prompt snapshot files without writing them. */
export async function createHappyPathPromptSnapshotFiles(): Promise<PromptSnapshotFile[]> {
const codexApi = await loadCodexPromptSnapshotApi();
const scenarios = createScenarios(codexApi);
const files = [
{
path: path.join(CODEX_RUNTIME_HAPPY_PATH_PROMPT_SNAPSHOT_DIR, "README.md"),
content: renderReadme(scenarios),
},
...scenarios.map((scenario) => ({
path: path.join(CODEX_RUNTIME_HAPPY_PATH_PROMPT_SNAPSHOT_DIR, `${scenario.id}.md`),
content: renderScenarioSnapshot(codexApi, scenario),
})),
...scenarios.map((scenario) => ({
path: path.join(CODEX_RUNTIME_HAPPY_PATH_PROMPT_SNAPSHOT_DIR, scenario.toolSnapshotFile),
content: stableJson(scenario.dynamicTools),
})),
];
return files.map((file) => ({
path: file.path,
content: file.content.endsWith("\n") ? file.content : `${file.content}\n`,
}));
}

View File

@@ -0,0 +1,16 @@
// Simple LLM stream mock helper builds deterministic streamed responses.
import { vi } from "vitest";
type LlmMockModule = Record<string, unknown>;
export function createLlmStreamSimpleMock(): LlmMockModule {
return {
streamSimple: vi.fn(() => ({
push: vi.fn(),
result: vi.fn(async () => undefined),
[Symbol.asyncIterator]: vi.fn(async function* () {
// Minimal async stream shape for wrappers that patch iteration/result.
}),
})),
};
}

View File

@@ -0,0 +1,795 @@
// Prompt composition scenarios build reusable agent prompt fixtures.
import fs from "node:fs/promises";
import path from "node:path";
import {
appendBootstrapPromptWarning,
analyzeBootstrapBudget,
buildBootstrapInjectionStats,
buildBootstrapPromptWarning,
} from "../../../src/agents/bootstrap-budget.js";
import { resolveBootstrapContextForRun } from "../../../src/agents/bootstrap-files.js";
import { buildCurrentInboundPrompt } from "../../../src/agents/embedded-agent-runner/run/runtime-context-prompt.js";
import { buildEmbeddedSystemPrompt } from "../../../src/agents/embedded-agent-runner/system-prompt.js";
import { buildAgentSystemPrompt } from "../../../src/agents/system-prompt.js";
import { createStubTool } from "../../../src/agents/test-helpers/agent-tool-stubs.js";
import {
buildDirectChatContext,
buildGroupChatContext,
buildGroupIntro,
} from "../../../src/auto-reply/reply/groups.js";
import {
buildInboundMetaSystemPrompt,
buildInboundUserContextPrefix,
} from "../../../src/auto-reply/reply/inbound-meta.js";
import { buildReplyPromptEnvelope } from "../../../src/auto-reply/reply/prompt-prelude.js";
import type { TemplateContext } from "../../../src/auto-reply/templating.js";
import { SILENT_REPLY_TOKEN } from "../../../src/auto-reply/tokens.js";
import type { OpenClawConfig } from "../../../src/config/config.js";
import { makeTempWorkspace, writeWorkspaceFile } from "../../../src/test-helpers/workspace.js";
// Prompt composition scenarios for system/body prompt stability tests.
/** One turn in a prompt composition scenario. */
export type PromptScenarioTurn = {
id: string;
label: string;
systemPrompt: string;
bodyPrompt: string;
notes: string[];
};
/** Multi-turn prompt composition scenario fixture. */
export type PromptScenario = {
scenario: string;
focus: string;
expectedStableSystemAfterTurnIds: string[];
turns: PromptScenarioTurn[];
};
function buildCommonSystemParams(workspaceDir: string) {
const toolNames = [
"bash",
"read",
"edit",
"grep",
"glob",
"message",
"memory_search",
"memory_get",
"web_search",
"x_search",
"web_fetch",
];
return {
runtimeInfo: {
agentId: "main",
host: "cache-lab",
repoRoot: workspaceDir,
os: "Darwin 24.0.0",
arch: "arm64",
node: process.version,
model: "anthropic/claude-sonnet-4-6",
defaultModel: "anthropic/claude-sonnet-4-6",
shell: "zsh",
},
userTimezone: "America/Los_Angeles",
userTime: "Monday, March 16th, 2026 - 9:00 PM",
userTimeFormat: "12" as const,
toolNames,
};
}
function buildSystemPrompt(params: {
workspaceDir: string;
extraSystemPrompt?: string;
skillsPrompt?: string;
reactionGuidance?: { level: "minimal" | "extensive"; channel: string };
contextFiles?: Array<{ path: string; content: string }>;
silentReplyPromptMode?: "generic" | "none";
}) {
const { runtimeInfo, userTimezone, userTime, userTimeFormat, toolNames } =
buildCommonSystemParams(params.workspaceDir);
return buildAgentSystemPrompt({
workspaceDir: params.workspaceDir,
extraSystemPrompt: params.extraSystemPrompt,
runtimeInfo,
userTimezone,
userTime,
userTimeFormat,
toolNames,
modelAliasLines: [],
promptMode: "full",
silentReplyPromptMode: params.silentReplyPromptMode,
acpEnabled: true,
skillsPrompt: params.skillsPrompt,
reactionGuidance: params.reactionGuidance,
contextFiles: params.contextFiles,
});
}
function buildAutoReplyBody(params: { ctx: TemplateContext; body: string; eventLine?: string }) {
return [params.eventLine, buildInboundUserContextPrefix(params.ctx), params.body]
.filter(Boolean)
.join("\n\n");
}
function buildAutoReplyModelPrompt(params: { ctx: TemplateContext; body: string }): string {
const inboundUserContext = buildInboundUserContextPrefix(params.ctx);
const envelope = buildReplyPromptEnvelope({
ctx: params.ctx,
sessionCtx: params.ctx,
baseBody: params.body,
hasUserBody: true,
inboundUserContext,
isBareSessionReset: false,
startupAction: "new",
});
return buildCurrentInboundPrompt({
context: envelope.currentInboundContext,
prompt: envelope.queuedBody,
});
}
async function readContextFiles(workspaceDir: string, fileNames: string[]) {
return Promise.all(
fileNames.map(async (fileName) => ({
path: fileName,
content: await fs.readFile(path.join(workspaceDir, fileName), "utf-8"),
})),
);
}
function buildAutoReplySystemPrompt(params: {
workspaceDir: string;
sessionCtx: TemplateContext;
includeGroupChatContext?: boolean;
includeGroupIntro?: boolean;
groupSystemPrompt?: string;
}) {
const extraSystemPromptParts = [
buildInboundMetaSystemPrompt(params.sessionCtx),
params.sessionCtx.ChatType === "direct" || params.sessionCtx.ChatType === "dm"
? buildDirectChatContext({
sessionCtx: params.sessionCtx,
})
: "",
params.includeGroupChatContext
? buildGroupChatContext({
sessionCtx: params.sessionCtx,
silentToken: SILENT_REPLY_TOKEN,
silentReplyPolicy: "allow",
})
: "",
params.includeGroupIntro
? buildGroupIntro({
defaultActivation: "mention",
})
: "",
params.groupSystemPrompt?.trim() ?? "",
].filter(Boolean);
return buildSystemPrompt({
workspaceDir: params.workspaceDir,
extraSystemPrompt: extraSystemPromptParts.join("\n\n") || undefined,
silentReplyPromptMode:
params.sessionCtx.ChatType === "direct" ||
params.sessionCtx.ChatType === "dm" ||
params.includeGroupChatContext
? "none"
: "generic",
});
}
function buildToolRichSystemPrompt(params: {
workspaceDir: string;
skillsPrompt: string;
contextFiles: Array<{ path: string; content: string }>;
}) {
const { runtimeInfo, userTimezone, userTime, userTimeFormat } = buildCommonSystemParams(
params.workspaceDir,
);
const tools = [
"bash",
"read",
"edit",
"grep",
"glob",
"message",
"memory_search",
"memory_get",
"web_search",
"x_search",
"web_fetch",
].map((name) => Object.assign({}, createStubTool(name), { description: `${name} tool` }));
return buildEmbeddedSystemPrompt({
workspaceDir: params.workspaceDir,
reasoningTagHint: false,
runtimeInfo,
tools,
modelAliasLines: [],
userTimezone,
userTime,
userTimeFormat,
acpEnabled: true,
skillsPrompt: params.skillsPrompt,
reactionGuidance: { level: "extensive", channel: "Telegram" },
contextFiles: params.contextFiles,
});
}
function createDirectScenario(workspaceDir: string): PromptScenario {
const baseCtx: TemplateContext = {
Provider: "slack",
Surface: "slack",
OriginatingChannel: "slack",
OriginatingTo: "D123",
AccountId: "A1",
ChatType: "dm",
SenderId: "U1",
SenderName: "Alice",
Body: "hi",
BodyStripped: "hi",
};
return {
scenario: "auto-reply-direct",
focus:
"Normal direct-chat turns with ids, reply context, think hint, and runtime event body injection",
expectedStableSystemAfterTurnIds: ["t2", "t3", "t4"],
turns: [
{
id: "t1",
label: "Direct turn with reply context",
systemPrompt: buildAutoReplySystemPrompt({
workspaceDir,
sessionCtx: {
...baseCtx,
MessageSid: "m1",
ReplyToId: "r1",
ReplyToBody: "prior message",
WasMentioned: true,
},
}),
bodyPrompt: buildAutoReplyBody({
ctx: {
...baseCtx,
MessageSid: "m1",
ReplyToId: "r1",
ReplyToBody: "prior message",
WasMentioned: true,
},
body: "Please summarize yesterday's decision.",
}),
notes: ["Direct chat baseline", "Per-message ids and reply context change in body only"],
},
{
id: "t2",
label: "Direct turn with new message id",
systemPrompt: buildAutoReplySystemPrompt({
workspaceDir,
sessionCtx: {
...baseCtx,
MessageSid: "m2",
ReplyToId: "r2",
},
}),
bodyPrompt: buildAutoReplyBody({
ctx: {
...baseCtx,
MessageSid: "m2",
ReplyToId: "r2",
},
body: "Now open the read tool and inspect AGENTS.md.",
}),
notes: ["Steady-state direct turn", "No runtime event"],
},
{
id: "t3",
label: "Direct turn with runtime event and think hint",
systemPrompt: buildAutoReplySystemPrompt({
workspaceDir,
sessionCtx: {
...baseCtx,
MessageSid: "m3",
ReplyToId: "r3",
},
}),
bodyPrompt: buildAutoReplyBody({
ctx: {
...baseCtx,
MessageSid: "m3",
ReplyToId: "r3",
},
eventLine: "System: [t] Model switched.",
body: "low use tools if needed and tell me which file controls startup behavior",
}),
notes: ["Touches runtime event body path", "Touches think-hint parsing path"],
},
{
id: "t4",
label: "Direct turn after runtime event",
systemPrompt: buildAutoReplySystemPrompt({
workspaceDir,
sessionCtx: {
...baseCtx,
MessageSid: "m4",
ReplyToId: "r4",
},
}),
bodyPrompt: buildAutoReplyBody({
ctx: {
...baseCtx,
MessageSid: "m4",
ReplyToId: "r4",
},
body: "Repeat the startup file path only.",
}),
notes: ["Checks steady-state after event turn"],
},
],
};
}
function createGroupScenario(workspaceDir: string): PromptScenario {
const baseCtx: TemplateContext = {
Provider: "slack",
Surface: "slack",
OriginatingChannel: "slack",
OriginatingTo: "C123",
AccountId: "A1",
ChatType: "group",
GroupSubject: "ops",
GroupChannel: "#ops",
GroupMembers: "Bob, Cara, Dan, Eve",
SenderId: "U2",
SenderName: "Bob",
Body: "hi",
BodyStripped: "hi",
};
return {
scenario: "auto-reply-group",
focus: "Group chat bootstrap, steady state, and runtime event turns",
expectedStableSystemAfterTurnIds: ["t2", "t3"],
turns: [
{
id: "t1",
label: "First group turn with session-stable intro",
systemPrompt: buildAutoReplySystemPrompt({
workspaceDir,
sessionCtx: {
...baseCtx,
MessageSid: "g1",
WasMentioned: true,
InboundHistory: [{ sender: "Cara", timestamp: 1, body: "status?" }],
},
includeGroupChatContext: true,
includeGroupIntro: true,
}),
bodyPrompt: buildAutoReplyBody({
ctx: {
...baseCtx,
MessageSid: "g1",
WasMentioned: true,
InboundHistory: [{ sender: "Cara", timestamp: 1, body: "status?" }],
},
body: "Can you investigate this issue?",
}),
notes: ["Group intro belongs to the session-stable system prompt"],
},
{
id: "t2",
label: "Steady-state group turn",
systemPrompt: buildAutoReplySystemPrompt({
workspaceDir,
sessionCtx: {
...baseCtx,
MessageSid: "g2",
WasMentioned: false,
InboundHistory: [
{ sender: "Cara", timestamp: 1, body: "status?" },
{ sender: "Dan", timestamp: 2, body: "please help" },
],
},
includeGroupChatContext: true,
includeGroupIntro: true,
}),
bodyPrompt: buildAutoReplyBody({
ctx: {
...baseCtx,
MessageSid: "g2",
WasMentioned: false,
InboundHistory: [
{ sender: "Cara", timestamp: 1, body: "status?" },
{ sender: "Dan", timestamp: 2, body: "please help" },
],
},
body: "Give a short update.",
}),
notes: ["Group intro remains stable after turn one"],
},
{
id: "t3",
label: "Group turn with runtime event",
systemPrompt: buildAutoReplySystemPrompt({
workspaceDir,
sessionCtx: {
...baseCtx,
MessageSid: "g2",
WasMentioned: false,
InboundHistory: [
{ sender: "Cara", timestamp: 1, body: "status?" },
{ sender: "Dan", timestamp: 2, body: "please help" },
],
},
includeGroupChatContext: true,
includeGroupIntro: true,
}),
bodyPrompt: buildAutoReplyBody({
ctx: {
...baseCtx,
MessageSid: "g3",
WasMentioned: true,
InboundHistory: [
{ sender: "Cara", timestamp: 1, body: "status?" },
{ sender: "Dan", timestamp: 2, body: "please help" },
{ sender: "Eve", timestamp: 3, body: "what changed?" },
],
},
eventLine: "System: [t] Node connected.",
body: "Tell the room whether tools are available.",
}),
notes: ["Runtime event lands in body", "System prompt should stay stable vs t2"],
},
],
};
}
function createDiscordBoundaryScenario(workspaceDir: string): PromptScenario {
const body = "Please summarize the deploy log.";
const baseCtx: TemplateContext = {
Provider: "discord",
Surface: "discord",
OriginatingChannel: "discord",
OriginatingTo: "channel:987654321",
AccountId: "A1",
ChatType: "channel",
GroupSubject: "#ops-bridge",
GroupChannel: "#ops-bridge",
GroupSpace: "guild-123",
SenderId: "U3",
SenderName: "Cael",
MessageSid: "1503084621145964846",
Body: body,
BodyStripped: body,
UntrustedStructuredContext: [
{
label: "Discord channel metadata",
source: "discord",
type: "channel_metadata",
payload: {
topic: "Deploy coordination",
},
},
],
};
return {
scenario: "auto-reply-discord-boundary",
focus:
"Discord inbound body remains one user turn while supplemental context is structured metadata",
expectedStableSystemAfterTurnIds: [],
turns: [
{
id: "t1",
label: "Discord turn with channel metadata",
systemPrompt: buildAutoReplySystemPrompt({
workspaceDir,
sessionCtx: baseCtx,
includeGroupChatContext: true,
}),
bodyPrompt: buildAutoReplyModelPrompt({
ctx: baseCtx,
body,
}),
notes: [
"Inbound body should appear once in the model-bound prompt",
"Channel metadata should not use raw EXTERNAL_UNTRUSTED_CONTENT wrappers",
],
},
],
};
}
async function createToolRichScenario(workspaceDir: string): Promise<PromptScenario> {
const skillsPrompt = [
"<available_skills>",
"<skill><name>checks</name><description>Run checks before landing changes.</description><location>/skills/checks/SKILL.md</location></skill>",
"<skill><name>release</name><description>Release OpenClaw safely.</description><location>/skills/release/SKILL.md</location></skill>",
"</available_skills>",
].join("\n");
const contextFiles = await readContextFiles(workspaceDir, ["AGENTS.md", "TOOLS.md", "SOUL.md"]);
const systemPrompt = buildToolRichSystemPrompt({
workspaceDir,
skillsPrompt,
contextFiles,
});
return {
scenario: "tool-rich-agent-run",
focus:
"Tool-enabled system prompt with skills, reactions, workspace bootstrap, and a follow-up after fictional tool calls",
expectedStableSystemAfterTurnIds: ["t2"],
turns: [
{
id: "t1",
label: "Tool-rich turn asking for search, read, and file edits",
systemPrompt,
bodyPrompt: [
"Conversation info (untrusted metadata):",
"```json",
JSON.stringify({ message_id: "tool-1", sender_id: "U9", was_mentioned: true }, null, 2),
"```",
"",
"high Search the workspace, read AGENTS.md, inspect the failing test, and propose a patch.",
].join("\n"),
notes: ["Touches tool list in system prompt", "Touches high-thinking hint in body"],
},
{
id: "t2",
label: "Follow-up after a fictional tool call",
systemPrompt,
bodyPrompt: [
"Conversation info (untrusted metadata):",
"```json",
JSON.stringify({ message_id: "tool-2", sender_id: "U9" }, null, 2),
"```",
"",
"Tool transcript summary (untrusted, for context):",
"```json",
JSON.stringify(
[
{ role: "assistant", action: "tool_use", name: "read", target: "AGENTS.md" },
{ role: "tool", name: "read", result: "Loaded AGENTS.md" },
{ role: "assistant", action: "tool_use", name: "grep", target: "failing test" },
{ role: "tool", name: "grep", result: "Matched src/foo.ts:42" },
],
null,
2,
),
"```",
"",
"Continue and explain the root cause.",
].join("\n"),
notes: ["Simulates tool-call-heavy conversation", "System prompt should stay stable"],
},
],
};
}
async function createBootstrapWarningScenario(workspaceDir: string): Promise<PromptScenario> {
const bootstrapConfig = {
agents: {
defaults: {
bootstrapMaxChars: 1_500,
bootstrapTotalMaxChars: 2_200,
},
},
} satisfies OpenClawConfig;
const largeAgents = "# AGENTS.md\n\n" + "Rules.\n".repeat(5_000);
const largeTools = "# TOOLS.md\n\n" + "Notes.\n".repeat(3_000);
await writeWorkspaceFile({ dir: workspaceDir, name: "AGENTS.md", content: largeAgents });
await writeWorkspaceFile({ dir: workspaceDir, name: "TOOLS.md", content: largeTools });
const { bootstrapFiles, contextFiles } = await resolveBootstrapContextForRun({
workspaceDir,
config: bootstrapConfig,
});
const analysis = analyzeBootstrapBudget({
files: buildBootstrapInjectionStats({
bootstrapFiles,
injectedFiles: contextFiles,
}),
bootstrapMaxChars: bootstrapConfig.agents.defaults.bootstrapMaxChars,
bootstrapTotalMaxChars: bootstrapConfig.agents.defaults.bootstrapTotalMaxChars,
});
if (!analysis.hasTruncation) {
throw new Error("bootstrap-warning scenario expected truncated bootstrap context");
}
const warningFirst = buildBootstrapPromptWarning({
analysis,
mode: "once",
seenSignatures: [],
});
const warningSeen = buildBootstrapPromptWarning({
analysis,
mode: "once",
seenSignatures: warningFirst.warningSignaturesSeen,
previousSignature: warningFirst.signature,
});
const warningAlways = buildBootstrapPromptWarning({
analysis,
mode: "always",
seenSignatures: warningFirst.warningSignaturesSeen,
previousSignature: warningFirst.signature,
});
return {
scenario: "bootstrap-warning",
focus: "Workspace bootstrap truncation warnings inside # Project Context",
expectedStableSystemAfterTurnIds: ["t2", "t3"],
turns: [
{
id: "t1",
label: "First warning emission",
systemPrompt: buildSystemPrompt({
workspaceDir,
contextFiles,
}),
bodyPrompt: appendBootstrapPromptWarning("hello", warningFirst.lines),
notes: ["Warning is appended to the turn body", "System prompt should stay stable"],
},
{
id: "t2",
label: "Same truncation signature after once-mode dedupe",
systemPrompt: buildSystemPrompt({
workspaceDir,
contextFiles,
}),
bodyPrompt: appendBootstrapPromptWarning("hello again", warningSeen.lines),
notes: ["Once-mode removes warning lines", "Only the body tail changes now"],
},
{
id: "t3",
label: "Always-mode warning",
systemPrompt: buildSystemPrompt({
workspaceDir,
contextFiles,
}),
bodyPrompt: appendBootstrapPromptWarning("one more turn", warningAlways.lines),
notes: [
"Always-mode keeps warning in the body prompt tail",
"System prompt remains stable",
],
},
],
};
}
async function createMaintenanceScenario(workspaceDir: string): Promise<PromptScenario> {
await writeWorkspaceFile({
dir: workspaceDir,
name: "AGENTS.md",
content: [
"## Session Startup",
"Read AGENTS.md and MEMORY.md before responding.",
"",
"## Red Lines",
"Do not delete production data.",
"",
"## Safety",
"Never reveal secrets.",
].join("\n"),
});
const memoryFlushPrompt = [
"Pre-compaction memory flush.",
"Store durable memories only in memory/2026-03-15.md (create memory/ if needed).",
"Treat workspace bootstrap/reference files such as MEMORY.md, SOUL.md, TOOLS.md, and AGENTS.md as read-only during this flush; never overwrite, replace, or edit them.",
"If nothing to store, reply with NO_REPLY.",
"Current time: Sunday, March 15th, 2026 - 9:30 PM (America/Los_Angeles)",
"Reference UTC: 2026-03-16 04:30 UTC",
].join("\n");
const memoryFlushSystemPrompt = buildSystemPrompt({
workspaceDir,
extraSystemPrompt: [
"Pre-compaction memory flush turn.",
"The session is near auto-compaction; capture durable memories to disk.",
"Store durable memories only in memory/YYYY-MM-DD.md (create memory/ if needed).",
"You may reply, but usually NO_REPLY is correct.",
].join(" "),
});
const postCompaction = [
"[Post-compaction context refresh]",
"",
"Session was just compacted. The conversation summary above is a hint, NOT a substitute for your startup sequence.",
"",
"Critical rules from AGENTS.md:",
"",
"## Session Startup",
"Read AGENTS.md and MEMORY.md before responding.",
"",
"## Red Lines",
"Do not delete production data.",
"",
"Current time: Sunday, March 15th, 2026 - 9:30 PM (America/Los_Angeles)",
"Reference UTC: 2026-03-16 04:30 UTC",
].join("\n");
const postCompactionSystemPrompt = buildSystemPrompt({
workspaceDir,
extraSystemPrompt: buildInboundMetaSystemPrompt({
Provider: "slack",
Surface: "slack",
OriginatingChannel: "slack",
OriginatingTo: "D123",
AccountId: "A1",
ChatType: "direct",
}),
});
return {
scenario: "maintenance-prompts",
focus: "Memory flush and post-compaction maintenance prompts",
expectedStableSystemAfterTurnIds: [],
turns: [
{
id: "t1",
label: "Pre-compaction memory flush run",
systemPrompt: memoryFlushSystemPrompt,
bodyPrompt: memoryFlushPrompt,
notes: [
"Writes to memory/2026-03-15.md",
"Separate maintenance run; expected to differ from normal user turns",
],
},
{
id: "t2",
label: "Post-compaction refresh context run",
systemPrompt: postCompactionSystemPrompt,
bodyPrompt: postCompaction,
notes: [
"Separate maintenance context payload",
"Expected to differ from normal user turns",
],
},
],
};
}
/** Create a temp workspace with prompt composition context files. */
export async function createWorkspaceWithPromptCompositionFiles(): Promise<string> {
const workspaceDir = await makeTempWorkspace("openclaw-prompt-cache-");
await writeWorkspaceFile({
dir: workspaceDir,
name: "AGENTS.md",
content: [
"# AGENTS.md",
"",
"## Session Startup",
"Read AGENTS.md and TOOLS.md before making changes.",
"",
"## Red Lines",
"Do not rewrite user commits.",
].join("\n"),
});
await writeWorkspaceFile({
dir: workspaceDir,
name: "TOOLS.md",
content: "# TOOLS.md\n\nUse rg before grep.\n",
});
await writeWorkspaceFile({
dir: workspaceDir,
name: "SOUL.md",
content: "# SOUL.md\n\nBe concise but kind.\n",
});
return workspaceDir;
}
/** Create all prompt composition scenarios plus cleanup handles. */
export async function createPromptCompositionScenarios(): Promise<{
workspaceDir: string;
warningWorkspaceDir: string;
scenarios: PromptScenario[];
cleanup: () => Promise<void>;
}> {
const workspaceDir = await createWorkspaceWithPromptCompositionFiles();
const warningWorkspaceDir = await makeTempWorkspace("openclaw-prompt-cache-warning-");
const scenarios = [
createDirectScenario(workspaceDir),
createGroupScenario(workspaceDir),
createDiscordBoundaryScenario(workspaceDir),
await createToolRichScenario(workspaceDir),
await createBootstrapWarningScenario(warningWorkspaceDir),
await createMaintenanceScenario(workspaceDir),
];
return {
workspaceDir,
warningWorkspaceDir,
scenarios,
cleanup: async () => {
await fs.rm(workspaceDir, { recursive: true, force: true });
await fs.rm(warningWorkspaceDir, { recursive: true, force: true });
},
};
}

View File

@@ -0,0 +1,8 @@
// Shared prompt snapshot fixture directories.
/** Codex runtime happy-path prompt snapshot fixture directory. */
export const CODEX_RUNTIME_HAPPY_PATH_PROMPT_SNAPSHOT_DIR =
"test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path";
/** Codex model prompt fixture directory. */
export const CODEX_MODEL_PROMPT_FIXTURE_DIR =
"test/fixtures/agents/prompt-snapshots/codex-model-catalog";

View File

@@ -0,0 +1,39 @@
// Shared transport parameter contract fixtures for GPT-5 model tests.
/** Expected OpenAI GPT-5 transport defaults. */
export const OPENAI_GPT5_TRANSPORT_DEFAULTS = {
parallel_tool_calls: true,
text_verbosity: "low",
} as const;
/** OpenAI GPT-5 cases that should receive GPT transport defaults. */
export const OPENAI_GPT5_TRANSPORT_DEFAULT_CASES = [
{
provider: "openai",
modelId: "gpt-5.4",
},
{
provider: "openai",
modelId: "gpt-5.4",
},
] as const;
/** Non-OpenAI GPT-5 case that should not receive OpenAI defaults. */
export const NON_OPENAI_GPT5_TRANSPORT_CASE = {
provider: "openrouter",
modelId: "gpt-5.4",
} as const;
/** Payload APIs that support parallel_tool_calls in GPT tests. */
export const GPT_PARALLEL_TOOL_CALLS_PAYLOAD_APIS = [
"openai-completions",
"openai-responses",
"openai-chatgpt-responses",
"azure-openai-responses",
] as const;
/** Payload APIs unrelated to GPT parallel tool call defaults. */
export const UNRELATED_TOOL_CALLS_PAYLOAD_APIS = [
"anthropic-messages",
"google-generative-ai",
] as const;