Merge pull request #3942 from code-yeongyu/fix/3819-compaction-agent-token-cache

fix(compaction): ignore compaction agent updates
This commit is contained in:
YeonGyu-Kim
2026-05-11 13:30:49 +09:00
committed by GitHub
4 changed files with 120 additions and 1 deletions
+39
View File
@@ -235,6 +235,45 @@ describe("context-window-monitor", () => {
expect(output.output).toContain("context remaining")
})
// #given only a compaction agent summary message update is seen
// #when tool.execute.after checks context usage
// #then stale pre-compaction tokens should not create a context reminder
it("should ignore compaction-agent message updates when caching context usage", async () => {
const hook = createContextWindowMonitorHook(ctx as never)
const sessionID = "ses_compaction_agent_context"
await hook.event({
event: {
type: "message.updated",
properties: {
info: {
agent: "compaction",
role: "assistant",
sessionID,
providerID: "anthropic",
modelID: "claude-sonnet-4-6",
finish: true,
tokens: {
input: 150000,
output: 1000,
reasoning: 0,
cache: { read: 10000, write: 0 },
},
},
},
},
})
const output = { title: "", output: "original", metadata: null }
await hook["tool.execute.after"](
{ tool: "bash", sessionID, callID: "call_1" },
output
)
expect(output.output).toBe("original")
expect(ctx.client.session.messages).not.toHaveBeenCalled()
})
// #given session is deleted
// #when session.deleted event fires
// #then cached data should be cleaned up
+3
View File
@@ -3,6 +3,7 @@ import {
resolveActualContextLimit,
type ContextLimitModelCacheState,
} from "../shared/context-limit-resolver"
import { isCompactionAgent } from "../shared/compaction-marker"
import { createSystemDirective, SystemDirectiveTypes } from "../shared/system-directive"
const CONTEXT_WARNING_THRESHOLD = 0.70
@@ -94,6 +95,7 @@ export function createContextWindowMonitorHook(
if (event.type === "message.updated") {
const info = props?.info as {
agent?: unknown
role?: string
sessionID?: string
providerID?: string
@@ -103,6 +105,7 @@ export function createContextWindowMonitorHook(
} | undefined
if (!info || info.role !== "assistant" || !info.finish) return
if (isCompactionAgent(info.agent)) return
if (!info.sessionID || !info.providerID || !info.tokens) return
tokenCache.set(info.sessionID, {
+75 -1
View File
@@ -55,7 +55,9 @@ function setupImmediateTimeouts(): () => void {
globalThis.setTimeout = ((callback: (...args: unknown[]) => void, _delay?: number, ...args: unknown[]) => {
callback(...args)
return 1 as unknown as ReturnType<typeof setTimeout>
const timeoutID = originalSetTimeout(() => undefined, 0)
originalClearTimeout(timeoutID)
return timeoutID
}) as typeof setTimeout
globalThis.clearTimeout = (() => {}) as typeof clearTimeout
@@ -637,6 +639,78 @@ describe("preemptive-compaction", () => {
Date.now = originalNow
})
// #given compaction already succeeded for a session
// #when the compaction agent emits its summary message update
// #then it should not clear the compaction guard or trigger a duplicate summary
it("should ignore compaction-agent message updates after successful compaction", async () => {
const hook = createPreemptiveCompactionHook(ctx as never, {} as never)
const sessionID = "ses_compaction_agent_update"
await hook.event({
event: {
type: "message.updated",
properties: {
info: {
role: "assistant",
sessionID,
providerID: "anthropic",
modelID: "claude-sonnet-4-6",
finish: true,
tokens: {
input: 170000,
output: 0,
reasoning: 0,
cache: { read: 10000, write: 0 },
},
},
},
},
})
await hook["tool.execute.after"](
{ tool: "bash", sessionID, callID: "call_1" },
{ title: "", output: "test", metadata: null }
)
expect(ctx.client.session.summarize).toHaveBeenCalledTimes(1)
const originalNow = Date.now
try {
Date.now = () => originalNow() + 61_000
await hook.event({
event: {
type: "message.updated",
properties: {
info: {
agent: "compaction",
role: "assistant",
sessionID,
providerID: "anthropic",
modelID: "claude-sonnet-4-6",
finish: true,
tokens: {
input: 170000,
output: 0,
reasoning: 0,
cache: { read: 10000, write: 0 },
},
},
},
},
})
await hook["tool.execute.after"](
{ tool: "bash", sessionID, callID: "call_2" },
{ title: "", output: "test", metadata: null }
)
expect(ctx.client.session.summarize).toHaveBeenCalledTimes(1)
} finally {
Date.now = originalNow
}
})
// #given modelContextLimitsCache has model-specific limit (256k)
// #when tokens are above default 78% of 200k but below 78% of 256k
// #then should NOT trigger compaction
+3
View File
@@ -1,4 +1,5 @@
import type { OhMyOpenCodeConfig } from "../config"
import { isCompactionAgent } from "../shared/compaction-marker"
import type { ContextLimitModelCacheState } from "../shared/context-limit-resolver"
import { createPostCompactionDegradationMonitor } from "./preemptive-compaction-degradation-monitor"
@@ -70,6 +71,7 @@ export function createPreemptiveCompactionHook(
if (event.type === "message.updated") {
const info = props?.info as {
id?: string
agent?: unknown
role?: string
sessionID?: string
providerID?: string
@@ -80,6 +82,7 @@ export function createPreemptiveCompactionHook(
} | undefined
if (!info || info.role !== "assistant" || !info.finish || !info.sessionID) return
if (isCompactionAgent(info.agent)) return
if (info.providerID && info.tokens) {
tokenCache.set(info.sessionID, {