Merge pull request #3942 from code-yeongyu/fix/3819-compaction-agent-token-cache
fix(compaction): ignore compaction agent updates
This commit is contained in:
@@ -235,6 +235,45 @@ describe("context-window-monitor", () => {
|
|||||||
expect(output.output).toContain("context remaining")
|
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
|
// #given session is deleted
|
||||||
// #when session.deleted event fires
|
// #when session.deleted event fires
|
||||||
// #then cached data should be cleaned up
|
// #then cached data should be cleaned up
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
resolveActualContextLimit,
|
resolveActualContextLimit,
|
||||||
type ContextLimitModelCacheState,
|
type ContextLimitModelCacheState,
|
||||||
} from "../shared/context-limit-resolver"
|
} from "../shared/context-limit-resolver"
|
||||||
|
import { isCompactionAgent } from "../shared/compaction-marker"
|
||||||
import { createSystemDirective, SystemDirectiveTypes } from "../shared/system-directive"
|
import { createSystemDirective, SystemDirectiveTypes } from "../shared/system-directive"
|
||||||
|
|
||||||
const CONTEXT_WARNING_THRESHOLD = 0.70
|
const CONTEXT_WARNING_THRESHOLD = 0.70
|
||||||
@@ -94,6 +95,7 @@ export function createContextWindowMonitorHook(
|
|||||||
|
|
||||||
if (event.type === "message.updated") {
|
if (event.type === "message.updated") {
|
||||||
const info = props?.info as {
|
const info = props?.info as {
|
||||||
|
agent?: unknown
|
||||||
role?: string
|
role?: string
|
||||||
sessionID?: string
|
sessionID?: string
|
||||||
providerID?: string
|
providerID?: string
|
||||||
@@ -103,6 +105,7 @@ export function createContextWindowMonitorHook(
|
|||||||
} | undefined
|
} | undefined
|
||||||
|
|
||||||
if (!info || info.role !== "assistant" || !info.finish) return
|
if (!info || info.role !== "assistant" || !info.finish) return
|
||||||
|
if (isCompactionAgent(info.agent)) return
|
||||||
if (!info.sessionID || !info.providerID || !info.tokens) return
|
if (!info.sessionID || !info.providerID || !info.tokens) return
|
||||||
|
|
||||||
tokenCache.set(info.sessionID, {
|
tokenCache.set(info.sessionID, {
|
||||||
|
|||||||
@@ -55,7 +55,9 @@ function setupImmediateTimeouts(): () => void {
|
|||||||
|
|
||||||
globalThis.setTimeout = ((callback: (...args: unknown[]) => void, _delay?: number, ...args: unknown[]) => {
|
globalThis.setTimeout = ((callback: (...args: unknown[]) => void, _delay?: number, ...args: unknown[]) => {
|
||||||
callback(...args)
|
callback(...args)
|
||||||
return 1 as unknown as ReturnType<typeof setTimeout>
|
const timeoutID = originalSetTimeout(() => undefined, 0)
|
||||||
|
originalClearTimeout(timeoutID)
|
||||||
|
return timeoutID
|
||||||
}) as typeof setTimeout
|
}) as typeof setTimeout
|
||||||
|
|
||||||
globalThis.clearTimeout = (() => {}) as typeof clearTimeout
|
globalThis.clearTimeout = (() => {}) as typeof clearTimeout
|
||||||
@@ -637,6 +639,78 @@ describe("preemptive-compaction", () => {
|
|||||||
Date.now = originalNow
|
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)
|
// #given modelContextLimitsCache has model-specific limit (256k)
|
||||||
// #when tokens are above default 78% of 200k but below 78% of 256k
|
// #when tokens are above default 78% of 200k but below 78% of 256k
|
||||||
// #then should NOT trigger compaction
|
// #then should NOT trigger compaction
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { OhMyOpenCodeConfig } from "../config"
|
import type { OhMyOpenCodeConfig } from "../config"
|
||||||
|
import { isCompactionAgent } from "../shared/compaction-marker"
|
||||||
import type { ContextLimitModelCacheState } from "../shared/context-limit-resolver"
|
import type { ContextLimitModelCacheState } from "../shared/context-limit-resolver"
|
||||||
|
|
||||||
import { createPostCompactionDegradationMonitor } from "./preemptive-compaction-degradation-monitor"
|
import { createPostCompactionDegradationMonitor } from "./preemptive-compaction-degradation-monitor"
|
||||||
@@ -70,6 +71,7 @@ export function createPreemptiveCompactionHook(
|
|||||||
if (event.type === "message.updated") {
|
if (event.type === "message.updated") {
|
||||||
const info = props?.info as {
|
const info = props?.info as {
|
||||||
id?: string
|
id?: string
|
||||||
|
agent?: unknown
|
||||||
role?: string
|
role?: string
|
||||||
sessionID?: string
|
sessionID?: string
|
||||||
providerID?: string
|
providerID?: string
|
||||||
@@ -80,6 +82,7 @@ export function createPreemptiveCompactionHook(
|
|||||||
} | undefined
|
} | undefined
|
||||||
|
|
||||||
if (!info || info.role !== "assistant" || !info.finish || !info.sessionID) return
|
if (!info || info.role !== "assistant" || !info.finish || !info.sessionID) return
|
||||||
|
if (isCompactionAgent(info.agent)) return
|
||||||
|
|
||||||
if (info.providerID && info.tokens) {
|
if (info.providerID && info.tokens) {
|
||||||
tokenCache.set(info.sessionID, {
|
tokenCache.set(info.sessionID, {
|
||||||
|
|||||||
Reference in New Issue
Block a user