fix: skip post-compaction cache ttl marker

This commit is contained in:
Josh Lehman 2026-03-12 14:54:48 -07:00
parent a0e3f8d5ff
commit a4114a52bc
No known key found for this signature in database
GPG Key ID: D141B425AC7F876B
2 changed files with 126 additions and 0 deletions

View File

@ -62,6 +62,7 @@ Docs: https://docs.openclaw.ai
- Gateway/session discovery: discover disk-only and retired ACP session stores under custom templated `session.store` roots so ACP reconciliation, session-id/session-label targeting, and run-id fallback keep working after restart. (#44176) thanks @gumadeiras.
- Models/OpenRouter native ids: canonicalize native OpenRouter model keys across config writes, runtime lookups, fallback management, and `models list --plain`, and migrate legacy duplicated `openrouter/openrouter/...` config entries forward on write.
- Gateway/hooks: bucket hook auth failures by forwarded client IP behind trusted proxies and warn when `hooks.allowedAgentIds` leaves hook routing unrestricted.
- Agents/compaction: skip the post-compaction `cache-ttl` marker write when a compaction completed in the same attempt, preventing the next turn from immediately triggering a second tiny compaction. (#28548) thanks @MoerAI.
## 2026.3.11

View File

@ -257,6 +257,14 @@ const testModel = {
input: ["text"],
} as unknown as Model<Api>;
const cacheTtlEligibleModel = {
api: "anthropic",
provider: "anthropic",
compat: {},
contextWindow: 8192,
input: ["text"],
} as unknown as Model<Api>;
describe("runEmbeddedAttempt sessions_spawn workspace inheritance", () => {
const tempPaths: string[] = [];
@ -382,6 +390,123 @@ describe("runEmbeddedAttempt sessions_spawn workspace inheritance", () => {
});
});
describe("runEmbeddedAttempt cache-ttl tracking after compaction", () => {
const tempPaths: string[] = [];
beforeEach(() => {
hoisted.createAgentSessionMock.mockReset();
hoisted.sessionManagerOpenMock.mockReset().mockReturnValue(hoisted.sessionManager);
hoisted.resolveSandboxContextMock.mockReset();
hoisted.acquireSessionWriteLockMock.mockReset().mockResolvedValue({
release: async () => {},
});
hoisted.sessionManager.getLeafEntry.mockReset().mockReturnValue(null);
hoisted.sessionManager.branch.mockReset();
hoisted.sessionManager.resetLeaf.mockReset();
hoisted.sessionManager.buildSessionContext.mockReset().mockReturnValue({ messages: [] });
hoisted.sessionManager.appendCustomEntry.mockReset();
});
afterEach(async () => {
while (tempPaths.length > 0) {
const target = tempPaths.pop();
if (target) {
await fs.rm(target, { recursive: true, force: true });
}
}
});
async function runAttemptWithCacheTtl(compactionCount: number) {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-cache-ttl-workspace-"));
const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-cache-ttl-agent-"));
const sessionFile = path.join(workspaceDir, "session.jsonl");
tempPaths.push(workspaceDir, agentDir);
await fs.writeFile(sessionFile, "", "utf8");
hoisted.subscribeEmbeddedPiSessionMock.mockReset().mockImplementation(() => ({
...createSubscriptionMock(),
getCompactionCount: () => compactionCount,
}));
hoisted.createAgentSessionMock.mockImplementation(async () => {
const session: MutableSession = {
sessionId: "embedded-session",
messages: [],
isCompacting: false,
isStreaming: false,
agent: {
replaceMessages: (messages: unknown[]) => {
session.messages = [...messages];
},
},
prompt: async () => {
session.messages = [
...session.messages,
{ role: "assistant", content: "done", timestamp: 2 },
];
},
abort: async () => {},
dispose: () => {},
steer: async () => {},
};
return { session };
});
return await runEmbeddedAttempt({
sessionId: "embedded-session",
sessionKey: "agent:main:test-cache-ttl",
sessionFile,
workspaceDir,
agentDir,
config: {
agents: {
defaults: {
contextPruning: {
mode: "cache-ttl",
},
},
},
},
prompt: "hello",
timeoutMs: 10_000,
runId: `run-cache-ttl-${compactionCount}`,
provider: "anthropic",
modelId: "claude-sonnet-4-20250514",
model: cacheTtlEligibleModel,
authStorage: {} as AuthStorage,
modelRegistry: {} as ModelRegistry,
thinkLevel: "off",
senderIsOwner: true,
disableMessageTool: true,
});
}
it("skips cache-ttl append when compaction completed during the attempt", async () => {
const result = await runAttemptWithCacheTtl(1);
expect(result.promptError).toBeNull();
expect(hoisted.sessionManager.appendCustomEntry).not.toHaveBeenCalledWith(
"openclaw.cache-ttl",
expect.anything(),
);
});
it("appends cache-ttl when no compaction completed during the attempt", async () => {
const result = await runAttemptWithCacheTtl(0);
expect(result.promptError).toBeNull();
expect(hoisted.sessionManager.appendCustomEntry).toHaveBeenCalledWith(
"openclaw.cache-ttl",
expect.objectContaining({
provider: "anthropic",
modelId: "claude-sonnet-4-20250514",
timestamp: expect.any(Number),
}),
);
});
});
describe("runEmbeddedAttempt context engine sessionKey forwarding", () => {
const tempPaths: string[] = [];
const sessionKey = "agent:main:discord:channel:test-ctx-engine";