import { describe, expect, it } from "vitest" ;
import {
appendBootstrapPromptWarning,
analyzeBootstrapBudget,
buildBootstrapInjectionStats,
buildBootstrapPromptWarning,
buildBootstrapTruncationReportMeta,
buildBootstrapTruncationSignature,
formatBootstrapTruncationWarningLines,
resolveBootstrapWarningSignaturesSeen,
} from "./bootstrap-budget.js" ;
import { buildAgentSystemPrompt } from "./system-prompt.js" ;
import type { WorkspaceBootstrapFile } from "./workspace.js" ;
describe("buildBootstrapInjectionStats" , () => {
it("maps raw and injected sizes and marks truncation" , () => {
const bootstrapFiles: WorkspaceBootstrapFile[] = [
{
name: "AGENTS.md" ,
path: "/tmp/AGENTS.md" ,
content: "a" .repeat(100 ),
missing: false ,
},
{
name: "SOUL.md" ,
path: "/tmp/SOUL.md" ,
content: "b" .repeat(50 ),
missing: false ,
},
];
const injectedFiles = [
{ path: "/tmp/AGENTS.md" , content: "a" .repeat(100 ) },
{ path: "/tmp/SOUL.md" , content: "b" .repeat(20 ) },
];
const stats = buildBootstrapInjectionStats({
bootstrapFiles,
injectedFiles,
});
expect(stats).toHaveLength(2 );
expect(stats[0 ]).toMatchObject({
name: "AGENTS.md" ,
rawChars: 100 ,
injectedChars: 100 ,
truncated: false ,
});
expect(stats[1 ]).toMatchObject({
name: "SOUL.md" ,
rawChars: 50 ,
injectedChars: 20 ,
truncated: true ,
});
});
});
describe("analyzeBootstrapBudget" , () => {
it("reports per-file and total-limit causes" , () => {
const analysis = analyzeBootstrapBudget({
files: [
{
name: "AGENTS.md" ,
path: "/tmp/AGENTS.md" ,
missing: false ,
rawChars: 150 ,
injectedChars: 120 ,
truncated: true ,
},
{
name: "SOUL.md" ,
path: "/tmp/SOUL.md" ,
missing: false ,
rawChars: 90 ,
injectedChars: 80 ,
truncated: true ,
},
],
bootstrapMaxChars: 120 ,
bootstrapTotalMaxChars: 200 ,
});
expect(analysis.hasTruncation).toBe(true );
expect(analysis.totalNearLimit).toBe(true );
expect(analysis.truncatedFiles).toHaveLength(2 );
const agents = analysis.truncatedFiles.find((file) => file.name === "AGENTS.md" );
const soul = analysis.truncatedFiles.find((file) => file.name === "SOUL.md" );
expect(agents?.causes).toContain("per-file-limit" );
expect(agents?.causes).toContain("total-limit" );
expect(soul?.causes).toContain("total-limit" );
});
it("does not force a total-limit cause when totals are within limits" , () => {
const analysis = analyzeBootstrapBudget({
files: [
{
name: "AGENTS.md" ,
path: "/tmp/AGENTS.md" ,
missing: false ,
rawChars: 90 ,
injectedChars: 40 ,
truncated: true ,
},
],
bootstrapMaxChars: 120 ,
bootstrapTotalMaxChars: 200 ,
});
expect(analysis.truncatedFiles[0 ]?.causes).toEqual([]);
});
});
describe("bootstrap prompt warnings" , () => {
it("appends warning details to the turn prompt instead of mutating the system prompt" , () => {
const prompt = appendBootstrapPromptWarning("Please continue." , [
"AGENTS.md: 200 raw -> 0 injected" ,
]);
expect(prompt.startsWith("Please continue." )).toBe(true );
expect(prompt).toContain("[Bootstrap truncation warning]" );
expect(prompt).toContain("Treat Project Context as partial" );
expect(prompt).toContain("- AGENTS.md: 200 raw -> 0 injected" );
expect(prompt.endsWith("- AGENTS.md: 200 raw -> 0 injected" )).toBe(true );
});
it("preserves raw prompt whitespace when appending warning details" , () => {
const prompt = appendBootstrapPromptWarning(" indented\nkeep tail " , [
"AGENTS.md: 200 raw -> 0 injected" ,
]);
expect(prompt).toContain(" indented\nkeep tail " );
expect(prompt.indexOf(" indented\nkeep tail " )).toBe(0 );
});
it("preserves exact heartbeat prompts without warning suffixes" , () => {
const heartbeatPrompt = "Read HEARTBEAT.md. Reply HEARTBEAT_OK." ;
expect(
appendBootstrapPromptWarning(heartbeatPrompt, ["AGENTS.md: 200 raw -> 0 injected" ], {
preserveExactPrompt: heartbeatPrompt,
}),
).toBe(heartbeatPrompt);
});
it("resolves seen signatures from report history or legacy single signature" , () => {
expect(
resolveBootstrapWarningSignaturesSeen({
bootstrapTruncation: {
warningSignaturesSeen: ["sig-a" , " " , "sig-b" , "sig-a" ],
promptWarningSignature: "legacy-ignored" ,
},
}),
).toEqual(["sig-a" , "sig-b" ]);
expect(
resolveBootstrapWarningSignaturesSeen({
bootstrapTruncation: {
promptWarningSignature: "legacy-only" ,
},
}),
).toEqual(["legacy-only" ]);
expect(resolveBootstrapWarningSignaturesSeen(undefined)).toEqual([]);
});
it("ignores single-signature fallback when warning mode is off" , () => {
expect(
resolveBootstrapWarningSignaturesSeen({
bootstrapTruncation: {
warningMode: "off" ,
promptWarningSignature: "off-mode-signature" ,
},
}),
).toEqual([]);
expect(
resolveBootstrapWarningSignaturesSeen({
bootstrapTruncation: {
warningMode: "off" ,
warningSignaturesSeen: ["prior-once-signature" ],
promptWarningSignature: "off-mode-signature" ,
},
}),
).toEqual(["prior-once-signature" ]);
});
it("dedupes warnings in once mode by signature" , () => {
const analysis = analyzeBootstrapBudget({
files: [
{
name: "AGENTS.md" ,
path: "/tmp/AGENTS.md" ,
missing: false ,
rawChars: 150 ,
injectedChars: 100 ,
truncated: true ,
},
],
bootstrapMaxChars: 120 ,
bootstrapTotalMaxChars: 200 ,
});
const first = buildBootstrapPromptWarning({
analysis,
mode: "once" ,
});
expect(first.warningShown).toBe(true );
expect(first.signature).toBeTruthy();
expect(first.lines.join("\n" )).toContain("AGENTS.md" );
const second = buildBootstrapPromptWarning({
analysis,
mode: "once" ,
seenSignatures: first.warningSignaturesSeen,
});
expect(second.warningShown).toBe(false );
expect(second.lines).toEqual([]);
});
it("dedupes once mode across non-consecutive repeated signatures" , () => {
const analysisA = analyzeBootstrapBudget({
files: [
{
name: "A.md" ,
path: "/tmp/A.md" ,
missing: false ,
rawChars: 150 ,
injectedChars: 100 ,
truncated: true ,
},
],
bootstrapMaxChars: 120 ,
bootstrapTotalMaxChars: 200 ,
});
const analysisB = analyzeBootstrapBudget({
files: [
{
name: "B.md" ,
path: "/tmp/B.md" ,
missing: false ,
rawChars: 150 ,
injectedChars: 100 ,
truncated: true ,
},
],
bootstrapMaxChars: 120 ,
bootstrapTotalMaxChars: 200 ,
});
const firstA = buildBootstrapPromptWarning({
analysis: analysisA,
mode: "once" ,
});
expect(firstA.warningShown).toBe(true );
const firstB = buildBootstrapPromptWarning({
analysis: analysisB,
mode: "once" ,
seenSignatures: firstA.warningSignaturesSeen,
});
expect(firstB.warningShown).toBe(true );
const secondA = buildBootstrapPromptWarning({
analysis: analysisA,
mode: "once" ,
seenSignatures: firstB.warningSignaturesSeen,
});
expect(secondA.warningShown).toBe(false );
});
it("includes overflow line when more files are truncated than shown" , () => {
const analysis = analyzeBootstrapBudget({
files: [
{
name: "A.md" ,
path: "/tmp/A.md" ,
missing: false ,
rawChars: 10 ,
injectedChars: 1 ,
truncated: true ,
},
{
name: "B.md" ,
path: "/tmp/B.md" ,
missing: false ,
rawChars: 10 ,
injectedChars: 1 ,
truncated: true ,
},
{
name: "C.md" ,
path: "/tmp/C.md" ,
missing: false ,
rawChars: 10 ,
injectedChars: 1 ,
truncated: true ,
},
],
bootstrapMaxChars: 20 ,
bootstrapTotalMaxChars: 10 ,
});
const lines = formatBootstrapTruncationWarningLines({
analysis,
maxFiles: 2 ,
});
expect(lines).toContain("+1 more truncated file(s)." );
});
it("disambiguates duplicate file names in warning lines" , () => {
const analysis = analyzeBootstrapBudget({
files: [
{
name: "AGENTS.md" ,
path: "/tmp/a/AGENTS.md" ,
missing: false ,
rawChars: 150 ,
injectedChars: 100 ,
truncated: true ,
},
{
name: "AGENTS.md" ,
path: "/tmp/b/AGENTS.md" ,
missing: false ,
rawChars: 140 ,
injectedChars: 100 ,
truncated: true ,
},
],
bootstrapMaxChars: 120 ,
bootstrapTotalMaxChars: 300 ,
});
const lines = formatBootstrapTruncationWarningLines({
analysis,
});
expect(lines.join("\n" )).toContain("AGENTS.md (/tmp/a/AGENTS.md)" );
expect(lines.join("\n" )).toContain("AGENTS.md (/tmp/b/AGENTS.md)" );
});
it("respects off/always warning modes" , () => {
const analysis = analyzeBootstrapBudget({
files: [
{
name: "AGENTS.md" ,
path: "/tmp/AGENTS.md" ,
missing: false ,
rawChars: 150 ,
injectedChars: 100 ,
truncated: true ,
},
],
bootstrapMaxChars: 120 ,
bootstrapTotalMaxChars: 200 ,
});
const signature = buildBootstrapTruncationSignature(analysis);
const off = buildBootstrapPromptWarning({
analysis,
mode: "off" ,
seenSignatures: [signature ?? "" ],
previousSignature: signature,
});
expect(off.warningShown).toBe(false );
expect(off.lines).toEqual([]);
const always = buildBootstrapPromptWarning({
analysis,
mode: "always" ,
seenSignatures: [signature ?? "" ],
previousSignature: signature,
});
expect(always.warningShown).toBe(true );
expect(always.lines.length).toBeGreaterThan(0 );
});
it("uses file path in signature to avoid collisions for duplicate names" , () => {
const left = analyzeBootstrapBudget({
files: [
{
name: "AGENTS.md" ,
path: "/tmp/a/AGENTS.md" ,
missing: false ,
rawChars: 150 ,
injectedChars: 100 ,
truncated: true ,
},
],
bootstrapMaxChars: 120 ,
bootstrapTotalMaxChars: 200 ,
});
const right = analyzeBootstrapBudget({
files: [
{
name: "AGENTS.md" ,
path: "/tmp/b/AGENTS.md" ,
missing: false ,
rawChars: 150 ,
injectedChars: 100 ,
truncated: true ,
},
],
bootstrapMaxChars: 120 ,
bootstrapTotalMaxChars: 200 ,
});
expect(buildBootstrapTruncationSignature(left)).not.toBe(
buildBootstrapTruncationSignature(right),
);
});
it("builds truncation report metadata from analysis + warning decision" , () => {
const analysis = analyzeBootstrapBudget({
files: [
{
name: "AGENTS.md" ,
path: "/tmp/AGENTS.md" ,
missing: false ,
rawChars: 150 ,
injectedChars: 100 ,
truncated: true ,
},
],
bootstrapMaxChars: 120 ,
bootstrapTotalMaxChars: 200 ,
});
const warning = buildBootstrapPromptWarning({
analysis,
mode: "once" ,
});
const meta = buildBootstrapTruncationReportMeta({
analysis,
warningMode: "once" ,
warning,
});
expect(meta.warningMode).toBe("once" );
expect(meta.warningShown).toBe(true );
expect(meta.truncatedFiles).toBe(1 );
expect(meta.nearLimitFiles).toBeGreaterThanOrEqual(1 );
expect(meta.promptWarningSignature).toBeTruthy();
expect(meta.warningSignaturesSeen?.length).toBeGreaterThan(0 );
});
it("improves cache-relevant system prompt stability versus legacy warning injection" , () => {
const contextFiles = [{ path: "AGENTS.md" , content: "Follow AGENTS guidance." }];
const warningLines = ["AGENTS.md: 200 raw -> 0 injected" ];
const stableSystemPrompt = buildAgentSystemPrompt({
workspaceDir: "/tmp/openclaw" ,
contextFiles,
});
const optimizedTurns = [stableSystemPrompt, stableSystemPrompt, stableSystemPrompt];
const injectLegacyWarning = (prompt: string, lines: string[]) => {
const warningBlock = [
"⚠ Bootstrap truncation warning:" ,
...lines.map((line) => `- ${line}`),
"" ,
].join("\n" );
return prompt.replace("## AGENTS.md" , `${warningBlock}## AGENTS.md`);
};
const legacyTurns = [
injectLegacyWarning(optimizedTurns[0 ] ?? "" , warningLines),
optimizedTurns[1 ] ?? "" ,
injectLegacyWarning(optimizedTurns[2 ] ?? "" , warningLines),
];
const cacheHitRate = (turns: string[]) => {
const hits = turns.slice(1 ).filter((turn, index) => turn === turns[index]).length;
return hits / Math.max(1 , turns.length - 1 );
};
expect(cacheHitRate(legacyTurns)).toBe(0 );
expect(cacheHitRate(optimizedTurns)).toBe(1 );
expect(optimizedTurns[0 ]).not.toContain("⚠ Bootstrap truncation warning:" );
});
});
Messung V0.5 in Prozent C=98 H=96 G=96
¤ Dauer der Verarbeitung: 0.36 Sekunden
(vorverarbeitet am 2026-05-26)
¤
*© Formatika GbR, Deutschland