fix(agents): increment compaction counter on overflow-triggered compaction (#39123)

Co-authored-by: MumuTW <clothl47364@gmail.com>
This commit is contained in:
Peter Steinberger
2026-03-07 19:44:06 +00:00
parent 4c2cb73055
commit e4497234c7
4 changed files with 104 additions and 7 deletions

View File

@@ -263,6 +263,7 @@ Docs: https://docs.openclaw.ai
- Discord/model picker persistence check: add a short post-dispatch settle delay before reading back session model state so picker confirmations stop reporting false mismatch warnings after successful model switches. (#39105) Thanks @akropp.
- Agents/OpenAI WS compat store flag: omit `store` from `response.create` payloads when model compat sets `supportsStore: false`, preventing strict OpenAI-compatible providers from rejecting websocket requests with unknown-field errors. (#39113) Thanks @scoootscooob.
- Config/validation log sanitization: sanitize config-validation issue paths/messages before logging so control characters and ANSI escape sequences cannot inject misleading terminal output from crafted config content. (#39116) Thanks @powermaster888.
- Agents/compaction counter accuracy: count successful overflow-triggered auto-compactions (`willRetry=true`) in the compaction counter while still excluding aborted/no-result events, so `/status` reflects actual safeguard compaction activity. (#39123) Thanks @MumuTW.
## 2026.3.2

View File

@@ -40,11 +40,17 @@ export function handleAutoCompactionStart(ctx: EmbeddedPiSubscribeContext) {
export function handleAutoCompactionEnd(
ctx: EmbeddedPiSubscribeContext,
evt: AgentEvent & { willRetry?: unknown },
evt: AgentEvent & { willRetry?: unknown; result?: unknown; aborted?: unknown },
) {
ctx.state.compactionInFlight = false;
const willRetry = Boolean(evt.willRetry);
if (!willRetry) {
// Increment counter whenever compaction actually produced a result,
// regardless of willRetry. Overflow-triggered compaction sets willRetry=true
// (the framework retries the LLM request), but the compaction itself succeeded
// and context was trimmed — the counter must reflect that. (#38905)
const hasResult = evt.result != null;
const wasAborted = Boolean(evt.aborted);
if (hasResult && !wasAborted) {
ctx.incrementCompactionCount?.();
}
if (willRetry) {

View File

@@ -38,11 +38,26 @@ describe("subscribeEmbeddedPiSession", () => {
emit({ type: "auto_compaction_start" });
expect(subscription.getCompactionCount()).toBe(0);
emit({ type: "auto_compaction_end", willRetry: true });
// willRetry with result — counter IS incremented (overflow compaction succeeded)
emit({ type: "auto_compaction_end", willRetry: true, result: { summary: "s" } });
expect(subscription.getCompactionCount()).toBe(1);
// willRetry=false with result — counter incremented again
emit({ type: "auto_compaction_end", willRetry: false, result: { summary: "s2" } });
expect(subscription.getCompactionCount()).toBe(2);
});
it("does not count compaction when result is absent", async () => {
const { emit, subscription } = createSubscribedSessionHarness({
runId: "run-compaction-no-result",
});
// No result (e.g. aborted or cancelled) — counter stays at 0
emit({ type: "auto_compaction_end", willRetry: false, result: undefined });
expect(subscription.getCompactionCount()).toBe(0);
emit({ type: "auto_compaction_end", willRetry: false });
expect(subscription.getCompactionCount()).toBe(1);
emit({ type: "auto_compaction_end", willRetry: false, aborted: true });
expect(subscription.getCompactionCount()).toBe(0);
});
it("emits compaction events on the agent event bus", async () => {

View File

@@ -100,6 +100,7 @@ describe("compaction hook wiring", () => {
{
type: "auto_compaction_end",
willRetry: false,
result: { summary: "compacted" },
} as never,
);
@@ -122,7 +123,7 @@ describe("compaction hook wiring", () => {
});
});
it("does not call runAfterCompaction when willRetry is true", () => {
it("does not call runAfterCompaction when willRetry is true but still increments counter", () => {
hookMocks.runner.hasHooks.mockReturnValue(true);
const ctx = {
@@ -132,7 +133,8 @@ describe("compaction hook wiring", () => {
noteCompactionRetry: vi.fn(),
resetForCompactionRetry: vi.fn(),
maybeResolveCompactionWait: vi.fn(),
getCompactionCount: () => 0,
incrementCompactionCount: vi.fn(),
getCompactionCount: () => 1,
};
handleAutoCompactionEnd(
@@ -140,10 +142,13 @@ describe("compaction hook wiring", () => {
{
type: "auto_compaction_end",
willRetry: true,
result: { summary: "compacted" },
} as never,
);
expect(hookMocks.runner.runAfterCompaction).not.toHaveBeenCalled();
// Counter is incremented even with willRetry — compaction succeeded (#38905)
expect(ctx.incrementCompactionCount).toHaveBeenCalledTimes(1);
expect(ctx.noteCompactionRetry).toHaveBeenCalledTimes(1);
expect(ctx.resetForCompactionRetry).toHaveBeenCalledTimes(1);
expect(ctx.maybeResolveCompactionWait).not.toHaveBeenCalled();
@@ -154,6 +159,75 @@ describe("compaction hook wiring", () => {
});
});
it("does not increment counter when compaction was aborted", () => {
const ctx = {
params: { runId: "r3b", session: { messages: [] } },
state: { compactionInFlight: true },
log: { debug: vi.fn(), warn: vi.fn() },
maybeResolveCompactionWait: vi.fn(),
incrementCompactionCount: vi.fn(),
getCompactionCount: () => 0,
};
handleAutoCompactionEnd(
ctx as never,
{
type: "auto_compaction_end",
willRetry: false,
result: undefined,
aborted: true,
} as never,
);
expect(ctx.incrementCompactionCount).not.toHaveBeenCalled();
});
it("does not increment counter when compaction has result but was aborted", () => {
const ctx = {
params: { runId: "r3b2", session: { messages: [] } },
state: { compactionInFlight: true },
log: { debug: vi.fn(), warn: vi.fn() },
maybeResolveCompactionWait: vi.fn(),
incrementCompactionCount: vi.fn(),
getCompactionCount: () => 0,
};
handleAutoCompactionEnd(
ctx as never,
{
type: "auto_compaction_end",
willRetry: false,
result: { summary: "compacted" },
aborted: true,
} as never,
);
expect(ctx.incrementCompactionCount).not.toHaveBeenCalled();
});
it("does not increment counter when result is undefined", () => {
const ctx = {
params: { runId: "r3c", session: { messages: [] } },
state: { compactionInFlight: true },
log: { debug: vi.fn(), warn: vi.fn() },
maybeResolveCompactionWait: vi.fn(),
incrementCompactionCount: vi.fn(),
getCompactionCount: () => 0,
};
handleAutoCompactionEnd(
ctx as never,
{
type: "auto_compaction_end",
willRetry: false,
result: undefined,
aborted: false,
} as never,
);
expect(ctx.incrementCompactionCount).not.toHaveBeenCalled();
});
it("resets stale assistant usage after final compaction", () => {
const messages = [
{ role: "user", content: "hello" },
@@ -183,6 +257,7 @@ describe("compaction hook wiring", () => {
{
type: "auto_compaction_end",
willRetry: false,
result: { summary: "compacted" },
} as never,
);