fix: preserve compaction default fallback

This commit is contained in:
Josh Lehman
2026-03-06 13:13:41 -08:00
parent 8aa0137b9b
commit d4228d184f
3 changed files with 51 additions and 14 deletions

View File

@@ -27,6 +27,7 @@ Docs: https://docs.openclaw.ai
- CLI: make read-only SecretRef status flows degrade safely (#37023) thanks @joshavant.
- Docker/Podman extension dependency baking: add `OPENCLAW_EXTENSIONS` so container builds can preinstall selected bundled extension npm dependencies into the image for faster and more reproducible startup in container deployments. (#32223) Thanks @sallyom.
- Onboarding/web search: add provider selection step and full provider list in configure wizard, with SecretRef ref-mode support during onboarding. (#34009) Thanks @kesku and @thewilloftheshadow.
- Agents/compaction post-context configurability: add `agents.defaults.compaction.postCompactionSections` so deployments can choose which `AGENTS.md` sections are re-injected after compaction, while preserving legacy fallback behavior when the documented default pair is configured in any order. (#34556) thanks @efe-arv.
### Breaking

View File

@@ -354,6 +354,23 @@ Read WORKFLOW.md on startup.
expect(result).toContain("Be safe");
});
it("falls back to legacy sections when default sections are configured in a different order", async () => {
const content = `## Every Session\n\nDo startup things.\n\n## Safety\n\nBe safe.\n`;
fs.writeFileSync(path.join(tmpDir, "AGENTS.md"), content);
const cfg = {
agents: {
defaults: {
compaction: { postCompactionSections: ["Red Lines", "Session Startup"] },
},
},
} as OpenClawConfig;
const result = await readPostCompactionContext(tmpDir, cfg);
expect(result).not.toBeNull();
expect(result).toContain("Do startup things");
expect(result).toContain("Be safe");
expect(result).toContain("Execute your Session Startup sequence now");
});
it("custom section names are matched case-insensitively", async () => {
const content = `## WORKFLOW INIT\n\nInit things.\n`;
fs.writeFileSync(path.join(tmpDir, "AGENTS.md"), content);

View File

@@ -9,6 +9,35 @@ const MAX_CONTEXT_CHARS = 3000;
const DEFAULT_POST_COMPACTION_SECTIONS = ["Session Startup", "Red Lines"];
const LEGACY_POST_COMPACTION_SECTIONS = ["Every Session", "Safety"];
// Compare configured section names as a case-insensitive set so deployments can
// pin the documented defaults in any order without changing fallback semantics.
function matchesSectionSet(sectionNames: string[], expectedSections: string[]): boolean {
if (sectionNames.length !== expectedSections.length) {
return false;
}
const counts = new Map<string, number>();
for (const name of expectedSections) {
const normalized = name.trim().toLowerCase();
counts.set(normalized, (counts.get(normalized) ?? 0) + 1);
}
for (const name of sectionNames) {
const normalized = name.trim().toLowerCase();
const count = counts.get(normalized);
if (!count) {
return false;
}
if (count === 1) {
counts.delete(normalized);
} else {
counts.set(normalized, count - 1);
}
}
return counts.size === 0;
}
function formatDateStamp(nowMs: number, timezone: string): string {
const parts = new Intl.DateTimeFormat("en-US", {
timeZone: timezone,
@@ -74,13 +103,10 @@ export async function readPostCompactionContext(
// with older AGENTS.md templates. The fallback also applies when the user
// explicitly configures the default pair, so that pinning the documented
// defaults never silently changes behavior vs. leaving the field unset.
const isExplicitDefaults =
Array.isArray(configuredSections) &&
configuredSections.length === DEFAULT_POST_COMPACTION_SECTIONS.length &&
configuredSections.every(
(s, i) => s.toLowerCase() === DEFAULT_POST_COMPACTION_SECTIONS[i].toLowerCase(),
);
if (sections.length === 0 && (!Array.isArray(configuredSections) || isExplicitDefaults)) {
const isDefaultSections =
!Array.isArray(configuredSections) ||
matchesSectionSet(configuredSections, DEFAULT_POST_COMPACTION_SECTIONS);
if (sections.length === 0 && isDefaultSections) {
sections = extractSections(content, LEGACY_POST_COMPACTION_SECTIONS, foundSectionNames);
}
@@ -108,13 +134,6 @@ export async function readPostCompactionContext(
// "Session Startup" sequence explicitly. When custom sections are configured,
// use generic prose — referencing a hardcoded "Session Startup" sequence
// would be misleading for deployments that use different section names.
const isDefaultSections =
!Array.isArray(configuredSections) ||
(configuredSections.length === DEFAULT_POST_COMPACTION_SECTIONS.length &&
configuredSections.every(
(s, i) => s.toLowerCase() === DEFAULT_POST_COMPACTION_SECTIONS[i].toLowerCase(),
));
const prose = isDefaultSections
? "Session was just compacted. The conversation summary above is a hint, NOT a substitute for your startup sequence. " +
"Execute your Session Startup sequence now — read the required files before responding to the user."