mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:50:43 +00:00
refactor: centralize bootstrap system prompt assembly
Centralize embedded attempt system prompt assembly so override and default prompts share bootstrap Project Context handling and provider transforms. Make bootstrap context routing explicit: full bootstrap can enter system Project Context, while runtime/user-message context remains disabled.
This commit is contained in:
committed by
GitHub
parent
d5ecee2cf3
commit
786fdeb366
@@ -15,7 +15,8 @@ export type AttemptBootstrapRoutingInput = {
|
||||
|
||||
export type AttemptBootstrapRouting = {
|
||||
bootstrapMode: BootstrapMode;
|
||||
shouldStripBootstrapFromContext: boolean;
|
||||
includeBootstrapInSystemContext: boolean;
|
||||
includeBootstrapInRuntimeContext: boolean;
|
||||
};
|
||||
|
||||
export type AttemptWorkspaceBootstrapRoutingInput = Omit<
|
||||
@@ -25,10 +26,16 @@ export type AttemptWorkspaceBootstrapRoutingInput = Omit<
|
||||
isWorkspaceBootstrapPending: (workspaceDir: string) => Promise<boolean>;
|
||||
};
|
||||
|
||||
export function shouldStripBootstrapFromEmbeddedContext(_params: {
|
||||
export function resolveBootstrapContextTargets(params: {
|
||||
bootstrapMode: BootstrapMode;
|
||||
}): boolean {
|
||||
return _params.bootstrapMode !== "full";
|
||||
}): Pick<
|
||||
AttemptBootstrapRouting,
|
||||
"includeBootstrapInSystemContext" | "includeBootstrapInRuntimeContext"
|
||||
> {
|
||||
return {
|
||||
includeBootstrapInSystemContext: params.bootstrapMode === "full",
|
||||
includeBootstrapInRuntimeContext: false,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveAttemptBootstrapRouting(
|
||||
@@ -47,9 +54,7 @@ function resolveAttemptBootstrapRouting(
|
||||
|
||||
return {
|
||||
bootstrapMode,
|
||||
shouldStripBootstrapFromContext: shouldStripBootstrapFromEmbeddedContext({
|
||||
bootstrapMode,
|
||||
}),
|
||||
...resolveBootstrapContextTargets({ bootstrapMode }),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildAttemptSystemPrompt } from "./attempt-system-prompt.js";
|
||||
|
||||
const baseProviderTransform = {
|
||||
provider: "openai",
|
||||
workspaceDir: "/tmp/openclaw",
|
||||
context: {
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.5",
|
||||
promptMode: "full" as const,
|
||||
},
|
||||
};
|
||||
|
||||
describe("buildAttemptSystemPrompt", () => {
|
||||
it("preserves bootstrap Project Context when a system prompt override is configured", () => {
|
||||
const result = buildAttemptSystemPrompt({
|
||||
isRawModelRun: false,
|
||||
systemPromptOverrideText: "Custom override prompt.",
|
||||
embeddedSystemPrompt: {
|
||||
workspaceDir: "/tmp/openclaw",
|
||||
reasoningTagHint: false,
|
||||
runtimeInfo: {
|
||||
host: "test-host",
|
||||
os: "Darwin",
|
||||
arch: "arm64",
|
||||
node: "v22.0.0",
|
||||
model: "openai/gpt-5.5",
|
||||
},
|
||||
tools: [],
|
||||
modelAliasLines: [],
|
||||
userTimezone: "UTC",
|
||||
bootstrapMode: "full",
|
||||
bootstrapTruncationNotice: "Bootstrap context was truncated.",
|
||||
contextFiles: [
|
||||
{
|
||||
path: "/tmp/openclaw/BOOTSTRAP.md",
|
||||
content: "Reply with BOOTSTRAP_OK.",
|
||||
},
|
||||
{
|
||||
path: "/tmp/openclaw/USER.md",
|
||||
content: "User profile should stay in normal prompt context only.",
|
||||
},
|
||||
],
|
||||
},
|
||||
providerTransform: baseProviderTransform,
|
||||
});
|
||||
|
||||
expect(result.systemPrompt).toContain("Custom override prompt.");
|
||||
expect(result.systemPrompt).toContain("## Bootstrap Pending");
|
||||
expect(result.systemPrompt).toContain("BOOTSTRAP.md is included below in Project Context");
|
||||
expect(result.systemPrompt).toContain("## Bootstrap Context Notice");
|
||||
expect(result.systemPrompt).toContain("Bootstrap context was truncated.");
|
||||
expect(result.systemPrompt).toContain("# Project Context");
|
||||
expect(result.systemPrompt).toContain("## /tmp/openclaw/BOOTSTRAP.md");
|
||||
expect(result.systemPrompt).toContain("Reply with BOOTSTRAP_OK.");
|
||||
expect(result.systemPrompt).not.toContain("USER.md");
|
||||
});
|
||||
|
||||
it("omits system prompts for raw model probes", () => {
|
||||
const result = buildAttemptSystemPrompt({
|
||||
isRawModelRun: true,
|
||||
embeddedSystemPrompt: {
|
||||
workspaceDir: "/tmp/openclaw",
|
||||
reasoningTagHint: false,
|
||||
runtimeInfo: {
|
||||
host: "test-host",
|
||||
os: "Darwin",
|
||||
arch: "arm64",
|
||||
node: "v22.0.0",
|
||||
model: "openai/gpt-5.5",
|
||||
},
|
||||
tools: [],
|
||||
modelAliasLines: [],
|
||||
userTimezone: "UTC",
|
||||
bootstrapMode: "full",
|
||||
contextFiles: [
|
||||
{
|
||||
path: "/tmp/openclaw/BOOTSTRAP.md",
|
||||
content: "Reply with BOOTSTRAP_OK.",
|
||||
},
|
||||
],
|
||||
},
|
||||
providerTransform: baseProviderTransform,
|
||||
});
|
||||
|
||||
expect(result.baseSystemPrompt).toContain("BOOTSTRAP.md is included below in Project Context");
|
||||
expect(result.systemPrompt).toBe("");
|
||||
expect(result.systemPromptOverride()).toBe("");
|
||||
});
|
||||
});
|
||||
56
src/agents/pi-embedded-runner/run/attempt-system-prompt.ts
Normal file
56
src/agents/pi-embedded-runner/run/attempt-system-prompt.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import type { OpenClawConfig } from "../../../config/types.openclaw.js";
|
||||
import { transformProviderSystemPrompt } from "../../../plugins/provider-runtime.js";
|
||||
import type { ProviderTransformSystemPromptContext } from "../../../plugins/types.js";
|
||||
import { appendAgentBootstrapSystemPromptSupplement } from "../../system-prompt.js";
|
||||
import { buildEmbeddedSystemPrompt, createSystemPromptOverride } from "../system-prompt.js";
|
||||
|
||||
type EmbeddedSystemPromptParams = Parameters<typeof buildEmbeddedSystemPrompt>[0];
|
||||
|
||||
export type BuildAttemptSystemPromptParams = {
|
||||
isRawModelRun: boolean;
|
||||
systemPromptOverrideText?: string;
|
||||
embeddedSystemPrompt: EmbeddedSystemPromptParams;
|
||||
providerTransform: {
|
||||
provider: string;
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir: string;
|
||||
context: Omit<ProviderTransformSystemPromptContext, "systemPrompt">;
|
||||
};
|
||||
};
|
||||
|
||||
export type AttemptSystemPrompt = {
|
||||
baseSystemPrompt: string;
|
||||
systemPrompt: string;
|
||||
systemPromptOverride: (defaultPrompt?: string) => string;
|
||||
};
|
||||
|
||||
export function buildAttemptSystemPrompt(
|
||||
params: BuildAttemptSystemPromptParams,
|
||||
): AttemptSystemPrompt {
|
||||
const baseSystemPrompt = params.systemPromptOverrideText
|
||||
? appendAgentBootstrapSystemPromptSupplement({
|
||||
systemPrompt: params.systemPromptOverrideText,
|
||||
bootstrapMode: params.embeddedSystemPrompt.bootstrapMode,
|
||||
bootstrapTruncationNotice: params.embeddedSystemPrompt.bootstrapTruncationNotice,
|
||||
contextFiles: params.embeddedSystemPrompt.contextFiles,
|
||||
})
|
||||
: buildEmbeddedSystemPrompt(params.embeddedSystemPrompt);
|
||||
|
||||
const systemPrompt = params.isRawModelRun
|
||||
? ""
|
||||
: transformProviderSystemPrompt({
|
||||
provider: params.providerTransform.provider,
|
||||
config: params.providerTransform.config,
|
||||
workspaceDir: params.providerTransform.workspaceDir,
|
||||
context: {
|
||||
...params.providerTransform.context,
|
||||
systemPrompt: baseSystemPrompt,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
baseSystemPrompt,
|
||||
systemPrompt,
|
||||
systemPromptOverride: createSystemPromptOverride(systemPrompt),
|
||||
};
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
resolveBootstrapContextTargets,
|
||||
resolveAttemptWorkspaceBootstrapRouting,
|
||||
shouldStripBootstrapFromEmbeddedContext,
|
||||
} from "./attempt-bootstrap-routing.js";
|
||||
|
||||
describe("runEmbeddedAttempt bootstrap routing", () => {
|
||||
@@ -26,7 +26,8 @@ describe("runEmbeddedAttempt bootstrap routing", () => {
|
||||
expect(isWorkspaceBootstrapPending).toHaveBeenCalledWith(canonicalWorkspace);
|
||||
expect(isWorkspaceBootstrapPending).not.toHaveBeenCalledWith(sandboxWorkspace);
|
||||
expect(routing.bootstrapMode).toBe("none");
|
||||
expect(routing.shouldStripBootstrapFromContext).toBe(true);
|
||||
expect(routing.includeBootstrapInSystemContext).toBe(false);
|
||||
expect(routing.includeBootstrapInRuntimeContext).toBe(false);
|
||||
});
|
||||
|
||||
it("falls back to limited bootstrap wording when a primary run cannot read files", async () => {
|
||||
@@ -41,15 +42,25 @@ describe("runEmbeddedAttempt bootstrap routing", () => {
|
||||
});
|
||||
|
||||
expect(routing.bootstrapMode).toBe("limited");
|
||||
expect(routing.shouldStripBootstrapFromContext).toBe(true);
|
||||
expect(routing.includeBootstrapInSystemContext).toBe(false);
|
||||
expect(routing.includeBootstrapInRuntimeContext).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps BOOTSTRAP.md in Project Context for full bootstrap turns", () => {
|
||||
expect(shouldStripBootstrapFromEmbeddedContext({ bootstrapMode: "full" })).toBe(false);
|
||||
expect(resolveBootstrapContextTargets({ bootstrapMode: "full" })).toEqual({
|
||||
includeBootstrapInSystemContext: true,
|
||||
includeBootstrapInRuntimeContext: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("strips BOOTSTRAP.md from Project Context outside full bootstrap turns", () => {
|
||||
expect(shouldStripBootstrapFromEmbeddedContext({ bootstrapMode: "limited" })).toBe(true);
|
||||
expect(shouldStripBootstrapFromEmbeddedContext({ bootstrapMode: "none" })).toBe(true);
|
||||
it("excludes BOOTSTRAP.md from every context outside full bootstrap turns", () => {
|
||||
expect(resolveBootstrapContextTargets({ bootstrapMode: "limited" })).toEqual({
|
||||
includeBootstrapInSystemContext: false,
|
||||
includeBootstrapInRuntimeContext: false,
|
||||
});
|
||||
expect(resolveBootstrapContextTargets({ bootstrapMode: "none" })).toEqual({
|
||||
includeBootstrapInSystemContext: false,
|
||||
includeBootstrapInRuntimeContext: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import { describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../../../config/config.js";
|
||||
import { SYSTEM_PROMPT_CACHE_BOUNDARY } from "../../system-prompt-cache-boundary.js";
|
||||
import { buildAgentSystemPrompt } from "../../system-prompt.js";
|
||||
import { resolveBootstrapContextTargets } from "./attempt-bootstrap-routing.js";
|
||||
import {
|
||||
buildContextEnginePromptCacheInfo,
|
||||
buildAfterTurnRuntimeContext,
|
||||
@@ -24,7 +25,6 @@ import {
|
||||
resolveAttemptToolPolicyMessageProvider,
|
||||
resolvePromptBuildHookResult,
|
||||
resolvePromptModeForSession,
|
||||
shouldStripBootstrapFromEmbeddedContext,
|
||||
shouldWarnOnOrphanedUserRepair,
|
||||
wrapStreamFnRepairMalformedToolCallArguments,
|
||||
wrapStreamFnSanitizeMalformedToolCalls,
|
||||
@@ -404,11 +404,20 @@ describe("resolvePromptModeForSession", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("shouldStripBootstrapFromEmbeddedContext", () => {
|
||||
describe("resolveBootstrapContextTargets", () => {
|
||||
it("keeps BOOTSTRAP.md in system Project Context only for full bootstrap turns", () => {
|
||||
expect(shouldStripBootstrapFromEmbeddedContext({ bootstrapMode: "full" })).toBe(false);
|
||||
expect(shouldStripBootstrapFromEmbeddedContext({ bootstrapMode: "limited" })).toBe(true);
|
||||
expect(shouldStripBootstrapFromEmbeddedContext({ bootstrapMode: "none" })).toBe(true);
|
||||
expect(resolveBootstrapContextTargets({ bootstrapMode: "full" })).toEqual({
|
||||
includeBootstrapInSystemContext: true,
|
||||
includeBootstrapInRuntimeContext: false,
|
||||
});
|
||||
expect(resolveBootstrapContextTargets({ bootstrapMode: "limited" })).toEqual({
|
||||
includeBootstrapInSystemContext: false,
|
||||
includeBootstrapInRuntimeContext: false,
|
||||
});
|
||||
expect(resolveBootstrapContextTargets({ bootstrapMode: "none" })).toEqual({
|
||||
includeBootstrapInSystemContext: false,
|
||||
includeBootstrapInRuntimeContext: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ import {
|
||||
import {
|
||||
resolveProviderSystemPromptContribution,
|
||||
resolveProviderTextTransforms,
|
||||
transformProviderSystemPrompt,
|
||||
} from "../../../plugins/provider-runtime.js";
|
||||
import { getPluginToolMeta } from "../../../plugins/tools.js";
|
||||
import { isAcpSessionKey, isSubagentSessionKey } from "../../../routing/session-key.js";
|
||||
@@ -155,7 +154,6 @@ import {
|
||||
import { resolveSystemPromptOverride } from "../../system-prompt-override.js";
|
||||
import { buildSystemPromptParams } from "../../system-prompt-params.js";
|
||||
import { buildSystemPromptReport } from "../../system-prompt-report.js";
|
||||
import { appendAgentBootstrapSystemPromptSupplement } from "../../system-prompt.js";
|
||||
import { resolveAgentTimeoutMs } from "../../timeout.js";
|
||||
import {
|
||||
buildEmptyExplicitToolAllowlistError,
|
||||
@@ -213,11 +211,7 @@ import {
|
||||
resolveEmbeddedAgentBaseStreamFn,
|
||||
resolveEmbeddedAgentStreamFn,
|
||||
} from "../stream-resolution.js";
|
||||
import {
|
||||
applySystemPromptOverrideToSession,
|
||||
buildEmbeddedSystemPrompt,
|
||||
createSystemPromptOverride,
|
||||
} from "../system-prompt.js";
|
||||
import { applySystemPromptOverrideToSession } from "../system-prompt.js";
|
||||
import { dropReasoningFromHistory, dropThinkingBlocks } from "../thinking.js";
|
||||
import {
|
||||
collectAllowedToolNames,
|
||||
@@ -239,21 +233,18 @@ import { flushPendingToolResultsAfterIdle } from "../wait-for-idle-before-flush.
|
||||
import { abortable as abortableWithSignal } from "./abortable.js";
|
||||
import { createEmbeddedAgentSessionWithResourceLoader } from "./attempt-session.js";
|
||||
export { buildContextEnginePromptCacheInfo } from "./attempt.context-engine-helpers.js";
|
||||
import {
|
||||
resolveAttemptWorkspaceBootstrapRouting,
|
||||
shouldStripBootstrapFromEmbeddedContext,
|
||||
} from "./attempt-bootstrap-routing.js";
|
||||
export { shouldStripBootstrapFromEmbeddedContext } from "./attempt-bootstrap-routing.js";
|
||||
import {
|
||||
rotateTranscriptAfterCompaction,
|
||||
shouldRotateCompactionTranscript,
|
||||
} from "../compaction-successor-transcript.js";
|
||||
import { resolveAttemptWorkspaceBootstrapRouting } from "./attempt-bootstrap-routing.js";
|
||||
import { configureEmbeddedAttemptHttpRuntime } from "./attempt-http-runtime.js";
|
||||
import {
|
||||
createEmbeddedRunStageTracker,
|
||||
formatEmbeddedRunStageSummary,
|
||||
shouldWarnEmbeddedRunStageSummary,
|
||||
} from "./attempt-stage-timing.js";
|
||||
import { buildAttemptSystemPrompt } from "./attempt-system-prompt.js";
|
||||
import {
|
||||
assembleAttemptContextEngine,
|
||||
buildLoopPromptCacheInfo,
|
||||
@@ -958,7 +949,6 @@ export async function runEmbeddedAttempt(
|
||||
hasBootstrapFileAccess: bootstrapHasFileAccess,
|
||||
});
|
||||
const bootstrapMode = bootstrapRouting.bootstrapMode;
|
||||
const shouldStripBootstrapFromContext = bootstrapRouting.shouldStripBootstrapFromContext;
|
||||
const {
|
||||
bootstrapFiles: hookAdjustedBootstrapFiles,
|
||||
contextFiles: resolvedContextFiles,
|
||||
@@ -993,12 +983,12 @@ export async function runEmbeddedAttempt(
|
||||
sourceWorkspaceDir: resolvedWorkspace,
|
||||
targetWorkspaceDir: effectiveWorkspace,
|
||||
});
|
||||
const contextFiles = shouldStripBootstrapFromContext
|
||||
? remappedContextFiles.filter((file) => !/(^|[\\/])BOOTSTRAP\.md$/iu.test(file.path.trim()))
|
||||
: remappedContextFiles;
|
||||
const bootstrapFilesForInjectionStats = shouldStripBootstrapFromContext
|
||||
? hookAdjustedBootstrapFiles.filter((file) => file.name !== DEFAULT_BOOTSTRAP_FILENAME)
|
||||
: hookAdjustedBootstrapFiles;
|
||||
const contextFiles = bootstrapRouting.includeBootstrapInSystemContext
|
||||
? remappedContextFiles
|
||||
: remappedContextFiles.filter((file) => !/(^|[\\/])BOOTSTRAP\.md$/iu.test(file.path.trim()));
|
||||
const bootstrapFilesForInjectionStats = bootstrapRouting.includeBootstrapInSystemContext
|
||||
? hookAdjustedBootstrapFiles
|
||||
: hookAdjustedBootstrapFiles.filter((file) => file.name !== DEFAULT_BOOTSTRAP_FILENAME);
|
||||
const bootstrapMaxChars = resolveBootstrapMaxChars(params.config);
|
||||
const bootstrapTotalMaxChars = resolveBootstrapTotalMaxChars(params.config);
|
||||
const bootstrapAnalysis = analyzeBootstrapBudget({
|
||||
@@ -1291,71 +1281,66 @@ export async function runEmbeddedAttempt(
|
||||
config: params.config,
|
||||
agentId: sessionAgentId,
|
||||
});
|
||||
const builtAppendPrompt = systemPromptOverrideText
|
||||
? appendAgentBootstrapSystemPromptSupplement({
|
||||
systemPrompt: systemPromptOverrideText,
|
||||
bootstrapMode,
|
||||
bootstrapTruncationNotice,
|
||||
contextFiles,
|
||||
})
|
||||
: buildEmbeddedSystemPrompt({
|
||||
workspaceDir: effectiveWorkspace,
|
||||
defaultThinkLevel: params.thinkLevel,
|
||||
reasoningLevel: params.reasoningLevel ?? "off",
|
||||
extraSystemPrompt: params.extraSystemPrompt,
|
||||
ownerNumbers: params.ownerNumbers,
|
||||
ownerDisplay: ownerDisplay.ownerDisplay,
|
||||
ownerDisplaySecret: ownerDisplay.ownerDisplaySecret,
|
||||
reasoningTagHint,
|
||||
heartbeatPrompt,
|
||||
skillsPrompt: effectiveSkillsPrompt,
|
||||
docsPath: openClawReferences.docsPath ?? undefined,
|
||||
sourcePath: openClawReferences.sourcePath ?? undefined,
|
||||
ttsHint,
|
||||
workspaceNotes: workspaceNotes?.length ? workspaceNotes : undefined,
|
||||
reactionGuidance,
|
||||
promptMode: effectivePromptMode,
|
||||
sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
|
||||
silentReplyPromptMode: params.silentReplyPromptMode,
|
||||
acpEnabled: isAcpRuntimeSpawnAvailable({
|
||||
config: params.config,
|
||||
sandboxed: sandboxInfo?.enabled === true,
|
||||
}),
|
||||
nativeCommandGuidanceLines: listRegisteredPluginAgentPromptGuidance(),
|
||||
runtimeInfo,
|
||||
messageToolHints,
|
||||
sandboxInfo,
|
||||
tools: effectiveTools,
|
||||
modelAliasLines: buildModelAliasLines(params.config),
|
||||
userTimezone,
|
||||
userTime,
|
||||
userTimeFormat,
|
||||
contextFiles,
|
||||
bootstrapMode,
|
||||
bootstrapTruncationNotice,
|
||||
includeMemorySection: !activeContextEngine || activeContextEngine.info.id === "legacy",
|
||||
memoryCitationsMode: params.config?.memory?.citations,
|
||||
promptContribution,
|
||||
});
|
||||
const appendPrompt = isRawModelRun
|
||||
? ""
|
||||
: transformProviderSystemPrompt({
|
||||
provider: params.provider,
|
||||
const attemptSystemPrompt = buildAttemptSystemPrompt({
|
||||
isRawModelRun,
|
||||
systemPromptOverrideText,
|
||||
embeddedSystemPrompt: {
|
||||
workspaceDir: effectiveWorkspace,
|
||||
defaultThinkLevel: params.thinkLevel,
|
||||
reasoningLevel: params.reasoningLevel ?? "off",
|
||||
extraSystemPrompt: params.extraSystemPrompt,
|
||||
ownerNumbers: params.ownerNumbers,
|
||||
ownerDisplay: ownerDisplay.ownerDisplay,
|
||||
ownerDisplaySecret: ownerDisplay.ownerDisplaySecret,
|
||||
reasoningTagHint,
|
||||
heartbeatPrompt,
|
||||
skillsPrompt: effectiveSkillsPrompt,
|
||||
docsPath: openClawReferences.docsPath ?? undefined,
|
||||
sourcePath: openClawReferences.sourcePath ?? undefined,
|
||||
ttsHint,
|
||||
workspaceNotes: workspaceNotes?.length ? workspaceNotes : undefined,
|
||||
reactionGuidance,
|
||||
promptMode: effectivePromptMode,
|
||||
sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
|
||||
silentReplyPromptMode: params.silentReplyPromptMode,
|
||||
acpEnabled: isAcpRuntimeSpawnAvailable({
|
||||
config: params.config,
|
||||
sandboxed: sandboxInfo?.enabled === true,
|
||||
}),
|
||||
nativeCommandGuidanceLines: listRegisteredPluginAgentPromptGuidance(),
|
||||
runtimeInfo,
|
||||
messageToolHints,
|
||||
sandboxInfo,
|
||||
tools: effectiveTools,
|
||||
modelAliasLines: buildModelAliasLines(params.config),
|
||||
userTimezone,
|
||||
userTime,
|
||||
userTimeFormat,
|
||||
contextFiles,
|
||||
bootstrapMode,
|
||||
bootstrapTruncationNotice,
|
||||
includeMemorySection: !activeContextEngine || activeContextEngine.info.id === "legacy",
|
||||
memoryCitationsMode: params.config?.memory?.citations,
|
||||
promptContribution,
|
||||
},
|
||||
providerTransform: {
|
||||
provider: params.provider,
|
||||
config: params.config,
|
||||
workspaceDir: effectiveWorkspace,
|
||||
context: {
|
||||
config: params.config,
|
||||
agentDir: params.agentDir,
|
||||
workspaceDir: effectiveWorkspace,
|
||||
context: {
|
||||
config: params.config,
|
||||
agentDir: params.agentDir,
|
||||
workspaceDir: effectiveWorkspace,
|
||||
provider: params.provider,
|
||||
modelId: params.modelId,
|
||||
promptMode: effectivePromptMode,
|
||||
runtimeChannel,
|
||||
runtimeCapabilities,
|
||||
agentId: sessionAgentId,
|
||||
systemPrompt: builtAppendPrompt,
|
||||
},
|
||||
});
|
||||
provider: params.provider,
|
||||
modelId: params.modelId,
|
||||
promptMode: effectivePromptMode,
|
||||
runtimeChannel,
|
||||
runtimeCapabilities,
|
||||
agentId: sessionAgentId,
|
||||
},
|
||||
},
|
||||
});
|
||||
const appendPrompt = attemptSystemPrompt.systemPrompt;
|
||||
const systemPromptReport = buildSystemPromptReport({
|
||||
source: "run",
|
||||
generatedAt: Date.now(),
|
||||
@@ -1384,7 +1369,7 @@ export async function runEmbeddedAttempt(
|
||||
skillsPrompt,
|
||||
tools: effectiveTools,
|
||||
});
|
||||
const systemPromptOverride = createSystemPromptOverride(appendPrompt);
|
||||
const systemPromptOverride = attemptSystemPrompt.systemPromptOverride;
|
||||
let systemPromptText = systemPromptOverride();
|
||||
prepStages.mark("system-prompt");
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { SYSTEM_PROMPT_CACHE_BOUNDARY } from "./system-prompt-cache-boundary.js"
|
||||
import {
|
||||
appendAgentBootstrapSystemPromptSupplement,
|
||||
buildAgentBootstrapSystemContext,
|
||||
buildAgentBootstrapSystemPromptSections,
|
||||
buildAgentBootstrapSystemPromptSupplement,
|
||||
buildAgentSystemPrompt,
|
||||
buildRuntimeLine,
|
||||
@@ -1111,6 +1112,22 @@ describe("buildAgentBootstrapSystemContext", () => {
|
||||
});
|
||||
|
||||
describe("buildAgentBootstrapSystemPromptSupplement", () => {
|
||||
it("can render bootstrap guidance without duplicating Project Context", () => {
|
||||
const sections = buildAgentBootstrapSystemPromptSections({
|
||||
bootstrapMode: "full",
|
||||
bootstrapTruncationNotice: "Bootstrap context was truncated.",
|
||||
contextFiles: [{ path: "/tmp/openclaw/BOOTSTRAP.md", content: "Ask who I am." }],
|
||||
includeProjectContext: false,
|
||||
}).join("\n");
|
||||
|
||||
expect(sections).toContain("## Bootstrap Pending");
|
||||
expect(sections).toContain("BOOTSTRAP.md is included below in Project Context");
|
||||
expect(sections).toContain("## Bootstrap Context Notice");
|
||||
expect(sections).toContain("Bootstrap context was truncated.");
|
||||
expect(sections).not.toContain("## /tmp/openclaw/BOOTSTRAP.md");
|
||||
expect(sections).not.toContain("Ask who I am.");
|
||||
});
|
||||
|
||||
it("adds pending bootstrap guidance and BOOTSTRAP.md contents for override prompts", () => {
|
||||
const supplement = buildAgentBootstrapSystemPromptSupplement({
|
||||
bootstrapMode: "full",
|
||||
|
||||
@@ -264,6 +264,21 @@ export function buildAgentBootstrapSystemPromptSupplement(params: {
|
||||
bootstrapTruncationNotice?: string;
|
||||
contextFiles?: EmbeddedContextFile[];
|
||||
}): string | undefined {
|
||||
const supplement = buildAgentBootstrapSystemPromptSections({
|
||||
...params,
|
||||
includeProjectContext: true,
|
||||
})
|
||||
.join("\n")
|
||||
.trim();
|
||||
return supplement.length > 0 ? supplement : undefined;
|
||||
}
|
||||
|
||||
export function buildAgentBootstrapSystemPromptSections(params: {
|
||||
bootstrapMode?: BootstrapMode;
|
||||
bootstrapTruncationNotice?: string;
|
||||
contextFiles?: EmbeddedContextFile[];
|
||||
includeProjectContext?: boolean;
|
||||
}): string[] {
|
||||
const bootstrapFiles =
|
||||
params.bootstrapMode === "full"
|
||||
? sortContextFilesForPrompt(params.contextFiles ?? []).filter((file) =>
|
||||
@@ -280,7 +295,7 @@ export function buildAgentBootstrapSystemPromptSupplement(params: {
|
||||
if (bootstrapTruncationNotice) {
|
||||
lines.push("## Bootstrap Context Notice", bootstrapTruncationNotice, "");
|
||||
}
|
||||
if (bootstrapFiles.length > 0) {
|
||||
if (params.includeProjectContext === true && bootstrapFiles.length > 0) {
|
||||
lines.push(
|
||||
...buildProjectContextSection({
|
||||
files: bootstrapFiles,
|
||||
@@ -289,8 +304,7 @@ export function buildAgentBootstrapSystemPromptSupplement(params: {
|
||||
}),
|
||||
);
|
||||
}
|
||||
const supplement = lines.join("\n").trim();
|
||||
return supplement.length > 0 ? supplement : undefined;
|
||||
return lines;
|
||||
}
|
||||
|
||||
export function appendAgentBootstrapSystemPromptSupplement(params: {
|
||||
@@ -819,14 +833,12 @@ export function buildAgentSystemPrompt(params: {
|
||||
const orderedContextFiles = sortContextFilesForPrompt(validContextFiles);
|
||||
const stableContextFiles = orderedContextFiles.filter((file) => !isDynamicContextFile(file.path));
|
||||
const dynamicContextFiles = orderedContextFiles.filter((file) => isDynamicContextFile(file.path));
|
||||
const hasBootstrapFileInProjectContext = orderedContextFiles.some((file) =>
|
||||
isBootstrapContextFile(file.path),
|
||||
);
|
||||
const bootstrapSystemContext = buildAgentBootstrapSystemContext({
|
||||
const bootstrapSystemPromptSections = buildAgentBootstrapSystemPromptSections({
|
||||
bootstrapMode: params.bootstrapMode,
|
||||
hasBootstrapFileInProjectContext,
|
||||
bootstrapTruncationNotice: params.bootstrapTruncationNotice,
|
||||
contextFiles: orderedContextFiles,
|
||||
includeProjectContext: false,
|
||||
});
|
||||
const bootstrapTruncationNotice = params.bootstrapTruncationNotice?.trim();
|
||||
const stablePrefixCacheKey = hashStablePromptInput({
|
||||
workspaceDir: params.workspaceDir,
|
||||
promptMode,
|
||||
@@ -853,8 +865,7 @@ export function buildAgentSystemPrompt(params: {
|
||||
workspaceGuidance,
|
||||
workspaceNotes,
|
||||
bootstrapMode: params.bootstrapMode,
|
||||
bootstrapSystemContext,
|
||||
bootstrapTruncationNotice,
|
||||
bootstrapSystemPromptSections,
|
||||
docsPath: params.docsPath,
|
||||
sourcePath: params.sourcePath,
|
||||
skillsPrompt,
|
||||
@@ -1063,10 +1074,7 @@ export function buildAgentSystemPrompt(params: {
|
||||
...buildTimeSection({
|
||||
userTimezone,
|
||||
}),
|
||||
...bootstrapSystemContext,
|
||||
bootstrapTruncationNotice ? "## Bootstrap Context Notice" : "",
|
||||
bootstrapTruncationNotice ?? "",
|
||||
bootstrapTruncationNotice ? "" : "",
|
||||
...bootstrapSystemPromptSections,
|
||||
"## Workspace Files (injected)",
|
||||
"These user-editable files are loaded by OpenClaw and included below in Project Context.",
|
||||
"",
|
||||
|
||||
Reference in New Issue
Block a user