mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-04 04:40:25 +00:00
690 lines
22 KiB
TypeScript
690 lines
22 KiB
TypeScript
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 { buildEmbeddedSystemPrompt } from "../../../src/agents/pi-embedded-runner/system-prompt.js";
|
|
import { buildAgentSystemPrompt } from "../../../src/agents/system-prompt.js";
|
|
import { createStubTool } from "../../../src/agents/test-helpers/pi-tool-stubs.js";
|
|
import { buildGroupChatContext, buildGroupIntro } from "../../../src/auto-reply/reply/groups.js";
|
|
import {
|
|
buildInboundMetaSystemPrompt,
|
|
buildInboundUserContextPrefix,
|
|
} from "../../../src/auto-reply/reply/inbound-meta.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";
|
|
|
|
export type PromptScenarioTurn = {
|
|
id: string;
|
|
label: string;
|
|
systemPrompt: string;
|
|
bodyPrompt: string;
|
|
notes: string[];
|
|
};
|
|
|
|
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-5",
|
|
defaultModel: "anthropic/claude-sonnet-4-5",
|
|
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 }>;
|
|
}) {
|
|
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",
|
|
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");
|
|
}
|
|
|
|
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.includeGroupChatContext ? buildGroupChatContext({ sessionCtx: params.sessionCtx }) : "",
|
|
params.includeGroupIntro
|
|
? buildGroupIntro({
|
|
cfg: {} as OpenClawConfig,
|
|
sessionCtx: params.sessionCtx,
|
|
defaultActivation: "mention",
|
|
silentToken: SILENT_REPLY_TOKEN,
|
|
})
|
|
: "",
|
|
params.groupSystemPrompt?.trim() ?? "",
|
|
].filter(Boolean);
|
|
return buildSystemPrompt({
|
|
workspaceDir: params.workspaceDir,
|
|
extraSystemPrompt: extraSystemPromptParts.join("\n\n") || undefined,
|
|
});
|
|
}
|
|
|
|
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) => ({ ...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: "direct",
|
|
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: ["t3"],
|
|
turns: [
|
|
{
|
|
id: "t1",
|
|
label: "First group turn with one-time 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: ["Expected first-turn bootstrap churn", "Not steady-state"],
|
|
},
|
|
{
|
|
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,
|
|
}),
|
|
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: ["One-time intro gone", "Should settle afterward"],
|
|
},
|
|
{
|
|
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,
|
|
}),
|
|
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"],
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
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) / 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) / 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",
|
|
],
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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),
|
|
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 });
|
|
},
|
|
};
|
|
}
|