From 9fa1ea38e84b2d08355fb47845a16fea54284f37 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Fri, 3 Apr 2026 17:50:24 +0900 Subject: [PATCH 1/3] test: isolate recovery hook mocks from full suite --- .../recovery-hook.test-support.ts | 113 ++++++++++++++++++ .../recovery-hook.test.ts | 113 ++++-------------- 2 files changed, 138 insertions(+), 88 deletions(-) create mode 100644 src/hooks/anthropic-context-window-limit-recovery/recovery-hook.test-support.ts diff --git a/src/hooks/anthropic-context-window-limit-recovery/recovery-hook.test-support.ts b/src/hooks/anthropic-context-window-limit-recovery/recovery-hook.test-support.ts new file mode 100644 index 000000000..e394a0040 --- /dev/null +++ b/src/hooks/anthropic-context-window-limit-recovery/recovery-hook.test-support.ts @@ -0,0 +1,113 @@ +import { mock } from "bun:test" +import type { PluginInput } from "@opencode-ai/plugin" +import type { OhMyOpenCodeConfig } from "../../config" +import { createAnthropicContextWindowLimitRecoveryHook } from "./recovery-hook" + +type ExecuteCompactFn = typeof import("./executor").executeCompact +type GetLastAssistantFn = typeof import("./executor").getLastAssistant +type ParseAnthropicTokenLimitErrorFn = typeof import("./parser").parseAnthropicTokenLimitError + +export type MockLastAssistant = { + info: { + summary?: boolean + providerID: string + modelID: string + } + hasContent: boolean +} + +export const executeCompactMock = mock(async () => {}) +export const getLastAssistantMock = mock(async (): Promise => ({ + info: { + providerID: "anthropic", + modelID: "claude-sonnet-4-6", + }, + hasContent: true, +})) +export const parseAnthropicTokenLimitErrorMock = mock(() => ({ + currentTokens: 250000, + maxTokens: 200000, + errorType: "token_limit_exceeded", + providerID: "anthropic", + modelID: "claude-sonnet-4-6", +})) + +const pluginConfig = { + git_master: { + commit_footer: false, + include_co_authored_by: false, + git_env_prefix: "", + }, +} satisfies OhMyOpenCodeConfig + +export function createRecoveryHook() { + return createAnthropicContextWindowLimitRecoveryHook( + createMockContext(), + { + pluginConfig, + dependencies: { + executeCompact: executeCompactMock, + getLastAssistant: getLastAssistantMock, + log: () => {}, + parseAnthropicTokenLimitError: parseAnthropicTokenLimitErrorMock, + }, + } as never, + ) +} + +export function createMockContext(): PluginInput { + return { + client: { + session: { + messages: mock(() => Promise.resolve({ data: [] })), + }, + tui: { + showToast: mock(() => Promise.resolve()), + }, + }, + project: {} as never, + directory: "/tmp", + worktree: "/tmp", + serverUrl: new URL("http://localhost"), + $: {} as never, + } as never +} + +export function setupDelayedTimeoutMocks(): { + createUntrackedTimeout: () => ReturnType + restore: () => void + getClearTimeoutCalls: () => Array> + getScheduledTimeouts: () => Array> +} { + const originalSetTimeout = globalThis.setTimeout + const originalClearTimeout = globalThis.clearTimeout + const clearTimeoutCalls: Array> = [] + const scheduledTimeouts: Array> = [] + + function createTimeoutHandle(): ReturnType { + const timeoutID = originalSetTimeout(() => {}, 60_000) + originalClearTimeout(timeoutID) + return timeoutID + } + + globalThis.setTimeout = ((_: () => void, _delay?: number) => { + const timeoutID = createTimeoutHandle() + scheduledTimeouts.push(timeoutID) + return timeoutID + }) as typeof setTimeout + + globalThis.clearTimeout = ((timeoutID: ReturnType) => { + clearTimeoutCalls.push(timeoutID) + originalClearTimeout(timeoutID) + }) as typeof clearTimeout + + return { + createUntrackedTimeout: createTimeoutHandle, + restore: () => { + globalThis.setTimeout = originalSetTimeout + globalThis.clearTimeout = originalClearTimeout + }, + getClearTimeoutCalls: () => clearTimeoutCalls, + getScheduledTimeouts: () => scheduledTimeouts, + } +} diff --git a/src/hooks/anthropic-context-window-limit-recovery/recovery-hook.test.ts b/src/hooks/anthropic-context-window-limit-recovery/recovery-hook.test.ts index f30046962..4291bb754 100644 --- a/src/hooks/anthropic-context-window-limit-recovery/recovery-hook.test.ts +++ b/src/hooks/anthropic-context-window-limit-recovery/recovery-hook.test.ts @@ -1,81 +1,11 @@ -import { afterAll, afterEach, beforeEach, describe, expect, mock, test } from "bun:test" -import type { PluginInput } from "@opencode-ai/plugin" -import * as originalExecutor from "./executor" -import * as originalParser from "./parser" -import * as originalLogger from "../../shared/logger" - -const executeCompactMock = mock(async () => {}) -const getLastAssistantMock = mock(async () => ({ - info: { - providerID: "anthropic", - modelID: "claude-sonnet-4-6", - }, - hasContent: true, -})) -const parseAnthropicTokenLimitErrorMock = mock(() => ({ - providerID: "anthropic", - modelID: "claude-sonnet-4-6", -})) - -mock.module("./executor", () => ({ - executeCompact: executeCompactMock, - getLastAssistant: getLastAssistantMock, -})) - -mock.module("./parser", () => ({ - parseAnthropicTokenLimitError: parseAnthropicTokenLimitErrorMock, -})) - -mock.module("../../shared/logger", () => ({ - log: () => {}, -})) - -afterAll(() => { - mock.module("./executor", () => originalExecutor) - mock.module("./parser", () => originalParser) - mock.module("../../shared/logger", () => originalLogger) -}) - -function createMockContext(): PluginInput { - return { - client: { - session: { - messages: mock(() => Promise.resolve({ data: [] })), - }, - tui: { - showToast: mock(() => Promise.resolve()), - }, - }, - directory: "/tmp", - } as PluginInput -} - -function setupDelayedTimeoutMocks(): { - restore: () => void - getClearTimeoutCalls: () => Array> -} { - const originalSetTimeout = globalThis.setTimeout - const originalClearTimeout = globalThis.clearTimeout - const clearTimeoutCalls: Array> = [] - let timeoutCounter = 0 - - globalThis.setTimeout = ((_: () => void, _delay?: number) => { - timeoutCounter += 1 - return timeoutCounter as ReturnType - }) as typeof setTimeout - - globalThis.clearTimeout = ((timeoutID: ReturnType) => { - clearTimeoutCalls.push(timeoutID) - }) as typeof clearTimeout - - return { - restore: () => { - globalThis.setTimeout = originalSetTimeout - globalThis.clearTimeout = originalClearTimeout - }, - getClearTimeoutCalls: () => clearTimeoutCalls, - } -} +import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test" +import { + createRecoveryHook, + executeCompactMock, + getLastAssistantMock, + parseAnthropicTokenLimitErrorMock, + setupDelayedTimeoutMocks, +} from "./recovery-hook.test-support" describe("createAnthropicContextWindowLimitRecoveryHook", () => { beforeEach(() => { @@ -90,9 +20,12 @@ describe("createAnthropicContextWindowLimitRecoveryHook", () => { test("cancels pending timer when session.idle handles compaction first", async () => { //#given - const { restore, getClearTimeoutCalls } = setupDelayedTimeoutMocks() - const { createAnthropicContextWindowLimitRecoveryHook } = await import("./recovery-hook") - const hook = createAnthropicContextWindowLimitRecoveryHook(createMockContext()) + const { restore, getClearTimeoutCalls, getScheduledTimeouts } = setupDelayedTimeoutMocks() + let compactedSessionID: unknown + executeCompactMock.mockImplementationOnce(async (...args: unknown[]) => { + compactedSessionID = args[0] + }) + const hook = createRecoveryHook() try { //#when @@ -111,9 +44,9 @@ describe("createAnthropicContextWindowLimitRecoveryHook", () => { }) //#then - expect(getClearTimeoutCalls()).toEqual([1 as ReturnType]) + expect(getClearTimeoutCalls()).toEqual([getScheduledTimeouts()[0]]) expect(executeCompactMock).toHaveBeenCalledTimes(1) - expect(executeCompactMock.mock.calls[0]?.[0]).toBe("session-race") + expect(compactedSessionID).toBe("session-race") } finally { restore() } @@ -121,7 +54,11 @@ describe("createAnthropicContextWindowLimitRecoveryHook", () => { test("does not treat empty summary assistant messages as successful compaction", async () => { //#given - const { restore, getClearTimeoutCalls } = setupDelayedTimeoutMocks() + const { restore, getClearTimeoutCalls, getScheduledTimeouts } = setupDelayedTimeoutMocks() + let compactedSessionID: unknown + executeCompactMock.mockImplementationOnce(async (...args: unknown[]) => { + compactedSessionID = args[0] + }) getLastAssistantMock.mockResolvedValueOnce({ info: { summary: true, @@ -130,8 +67,7 @@ describe("createAnthropicContextWindowLimitRecoveryHook", () => { }, hasContent: false, }) - const { createAnthropicContextWindowLimitRecoveryHook } = await import("./recovery-hook") - const hook = createAnthropicContextWindowLimitRecoveryHook(createMockContext()) + const hook = createRecoveryHook() try { //#when @@ -150,11 +86,12 @@ describe("createAnthropicContextWindowLimitRecoveryHook", () => { }) //#then - expect(getClearTimeoutCalls()).toEqual([1 as ReturnType]) + expect(getClearTimeoutCalls()).toEqual([getScheduledTimeouts()[0]]) expect(executeCompactMock).toHaveBeenCalledTimes(1) - expect(executeCompactMock.mock.calls[0]?.[0]).toBe("session-empty-summary") + expect(compactedSessionID).toBe("session-empty-summary") } finally { restore() } }) + }) From fe326ca79b1be80797405f77ef7b6a766e2c2c81 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Fri, 3 Apr 2026 17:50:24 +0900 Subject: [PATCH 2/3] fix: clear stale recovery retry state --- .../recovery-hook-regression.test.ts | 179 ++++++++++++++++++ .../recovery-hook.ts | 65 +++++-- 2 files changed, 223 insertions(+), 21 deletions(-) create mode 100644 src/hooks/anthropic-context-window-limit-recovery/recovery-hook-regression.test.ts diff --git a/src/hooks/anthropic-context-window-limit-recovery/recovery-hook-regression.test.ts b/src/hooks/anthropic-context-window-limit-recovery/recovery-hook-regression.test.ts new file mode 100644 index 000000000..2d27b4a3c --- /dev/null +++ b/src/hooks/anthropic-context-window-limit-recovery/recovery-hook-regression.test.ts @@ -0,0 +1,179 @@ +import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test" +import type { AutoCompactState } from "./types" +import { + createRecoveryHook, + executeCompactMock, + getLastAssistantMock, + parseAnthropicTokenLimitErrorMock, + setupDelayedTimeoutMocks, +} from "./recovery-hook.test-support" + +function isAutoCompactState(value: unknown): value is AutoCompactState { + if (typeof value !== "object" || value === null) { + return false + } + + return ( + "pendingCompact" in value && + "errorDataBySession" in value && + "retryStateBySession" in value && + "retryTimerBySession" in value && + "truncateStateBySession" in value && + "emptyContentAttemptBySession" in value && + "compactionInProgress" in value + ) +} + +describe("createAnthropicContextWindowLimitRecoveryHook regressions", () => { + beforeEach(() => { + executeCompactMock.mockClear() + getLastAssistantMock.mockClear() + parseAnthropicTokenLimitErrorMock.mockClear() + }) + + afterEach(() => { + mock.restore() + }) + + test("clears older pending compaction timer before scheduling replacement for same session", async () => { + //#given + const { restore, getClearTimeoutCalls, getScheduledTimeouts } = setupDelayedTimeoutMocks() + const hook = createRecoveryHook() + + try { + //#when + await hook.event({ + event: { + type: "session.error", + properties: { sessionID: "session-retry-timer", error: "prompt is too long" }, + }, + }) + + await hook.event({ + event: { + type: "session.error", + properties: { sessionID: "session-retry-timer", error: "prompt is too long again" }, + }, + }) + + const [firstScheduledTimeout] = getScheduledTimeouts() + if (firstScheduledTimeout === undefined) { + throw new Error("Expected first scheduled timeout") + } + + //#then + expect(getClearTimeoutCalls()).toEqual([firstScheduledTimeout]) + expect(executeCompactMock).not.toHaveBeenCalled() + } finally { + restore() + } + }) + + test("fully clears recovery state when contentful summary already succeeded", async () => { + //#given + const { + restore, + createUntrackedTimeout, + getClearTimeoutCalls, + getScheduledTimeouts, + } = setupDelayedTimeoutMocks() + const sessionID = "session-summary-success" + let retryTimerHandle: ReturnType | undefined + let capturedAutoCompactState: AutoCompactState | undefined + executeCompactMock.mockImplementationOnce(async (...args: unknown[]) => { + const autoCompactState = args[2] + if (isAutoCompactState(autoCompactState)) { + capturedAutoCompactState = autoCompactState + } + }) + + const hook = createRecoveryHook() + + try { + await hook.event({ + event: { + type: "session.error", + properties: { sessionID, error: "prompt is too long" }, + }, + }) + + await hook.event({ + event: { + type: "session.idle", + properties: { sessionID }, + }, + }) + + expect(capturedAutoCompactState).toBeDefined() + + capturedAutoCompactState?.retryStateBySession.set(sessionID, { + attempt: 1, + lastAttemptTime: Date.now(), + firstAttemptTime: Date.now(), + }) + capturedAutoCompactState?.truncateStateBySession.set(sessionID, { + truncateAttempt: 2, + }) + capturedAutoCompactState?.emptyContentAttemptBySession.set(sessionID, 3) + capturedAutoCompactState?.retryTimerBySession.set( + sessionID, + (retryTimerHandle = createUntrackedTimeout()), + ) + + getLastAssistantMock.mockResolvedValueOnce({ + info: { + providerID: "anthropic", + modelID: "claude-sonnet-4-6", + }, + hasContent: true, + }) + await hook.event({ + event: { + type: "session.error", + properties: { sessionID, error: "prompt is too long again" }, + }, + }) + + getLastAssistantMock.mockResolvedValueOnce({ + info: { + summary: true, + providerID: "anthropic", + modelID: "claude-sonnet-4-6", + }, + hasContent: true, + }) + + //#when + await hook.event({ + event: { + type: "session.idle", + properties: { sessionID }, + }, + }) + + const [firstScheduledTimeout, secondScheduledTimeout] = getScheduledTimeouts() + if ( + firstScheduledTimeout === undefined || + secondScheduledTimeout === undefined || + retryTimerHandle === undefined + ) { + throw new Error("Expected scheduled timeout handles") + } + + //#then + expect(getClearTimeoutCalls()).toEqual([ + firstScheduledTimeout, + secondScheduledTimeout, + retryTimerHandle, + ]) + expect(capturedAutoCompactState?.pendingCompact.has(sessionID)).toBe(false) + expect(capturedAutoCompactState?.errorDataBySession.has(sessionID)).toBe(false) + expect(capturedAutoCompactState?.retryStateBySession.has(sessionID)).toBe(false) + expect(capturedAutoCompactState?.retryTimerBySession.has(sessionID)).toBe(false) + expect(capturedAutoCompactState?.truncateStateBySession.has(sessionID)).toBe(false) + expect(capturedAutoCompactState?.emptyContentAttemptBySession.has(sessionID)).toBe(false) + } finally { + restore() + } + }) +}) diff --git a/src/hooks/anthropic-context-window-limit-recovery/recovery-hook.ts b/src/hooks/anthropic-context-window-limit-recovery/recovery-hook.ts index 5ca26cfbb..2be7569ea 100644 --- a/src/hooks/anthropic-context-window-limit-recovery/recovery-hook.ts +++ b/src/hooks/anthropic-context-window-limit-recovery/recovery-hook.ts @@ -11,6 +11,12 @@ import { log } from "../../shared/logger" export interface AnthropicContextWindowLimitRecoveryOptions { experimental?: ExperimentalConfig pluginConfig: OhMyOpenCodeConfig + dependencies?: { + executeCompact?: typeof executeCompact + getLastAssistant?: typeof getLastAssistant + log?: typeof log + parseAnthropicTokenLimitError?: typeof parseAnthropicTokenLimitError + } } function createRecoveryState(): AutoCompactState { @@ -33,19 +39,30 @@ export function createAnthropicContextWindowLimitRecoveryHook( const autoCompactState = createRecoveryState() const experimental = options?.experimental const pluginConfig = options?.pluginConfig ?? {} as OhMyOpenCodeConfig + const dependencies = { + executeCompact, + getLastAssistant, + log, + parseAnthropicTokenLimitError, + ...options?.dependencies, + } const pendingCompactionTimeoutBySession = new Map>() + function clearPendingCompactionTimeout(sessionID: string): void { + const timeoutID = pendingCompactionTimeoutBySession.get(sessionID) + if (timeoutID !== undefined) { + clearTimeout(timeoutID) + pendingCompactionTimeoutBySession.delete(sessionID) + } + } + const eventHandler = async ({ event }: { event: { type: string; properties?: unknown } }) => { const props = event.properties as Record | undefined if (event.type === "session.deleted") { const sessionInfo = props?.info as { id?: string } | undefined if (sessionInfo?.id) { - const timeoutID = pendingCompactionTimeoutBySession.get(sessionInfo.id) - if (timeoutID !== undefined) { - clearTimeout(timeoutID) - pendingCompactionTimeoutBySession.delete(sessionInfo.id) - } + clearPendingCompactionTimeout(sessionInfo.id) clearSessionState(autoCompactState, sessionInfo.id) } @@ -54,11 +71,11 @@ export function createAnthropicContextWindowLimitRecoveryHook( if (event.type === "session.error") { const sessionID = props?.sessionID as string | undefined - log("[auto-compact] session.error received", { sessionID, error: props?.error }) + dependencies.log("[auto-compact] session.error received", { sessionID, error: props?.error }) if (!sessionID) return - const parsed = parseAnthropicTokenLimitError(props?.error) - log("[auto-compact] parsed result", { parsed, hasError: !!props?.error }) + const parsed = dependencies.parseAnthropicTokenLimitError(props?.error) + dependencies.log("[auto-compact] parsed result", { parsed, hasError: !!props?.error }) if (parsed) { autoCompactState.pendingCompact.add(sessionID) autoCompactState.errorDataBySession.set(sessionID, parsed) @@ -68,7 +85,11 @@ export function createAnthropicContextWindowLimitRecoveryHook( return } - const lastAssistant = await getLastAssistant(sessionID, ctx.client, ctx.directory) + const lastAssistant = await dependencies.getLastAssistant( + sessionID, + ctx.client, + ctx.directory, + ) const lastAssistantInfo = lastAssistant?.info const providerID = parsed.providerID ?? (lastAssistantInfo?.providerID as string | undefined) const modelID = parsed.modelID ?? (lastAssistantInfo?.modelID as string | undefined) @@ -84,9 +105,11 @@ export function createAnthropicContextWindowLimitRecoveryHook( }) .catch(() => {}) + clearPendingCompactionTimeout(sessionID) + const timeoutID = setTimeout(() => { pendingCompactionTimeoutBySession.delete(sessionID) - executeCompact( + dependencies.executeCompact( sessionID, { providerID, modelID }, autoCompactState, @@ -107,9 +130,9 @@ export function createAnthropicContextWindowLimitRecoveryHook( const sessionID = info?.sessionID as string | undefined if (sessionID && info?.role === "assistant" && info.error) { - log("[auto-compact] message.updated with error", { sessionID, error: info.error }) - const parsed = parseAnthropicTokenLimitError(info.error) - log("[auto-compact] message.updated parsed result", { parsed }) + dependencies.log("[auto-compact] message.updated with error", { sessionID, error: info.error }) + const parsed = dependencies.parseAnthropicTokenLimitError(info.error) + dependencies.log("[auto-compact] message.updated parsed result", { parsed }) if (parsed) { parsed.providerID = info.providerID as string | undefined parsed.modelID = info.modelID as string | undefined @@ -126,18 +149,18 @@ export function createAnthropicContextWindowLimitRecoveryHook( if (!autoCompactState.pendingCompact.has(sessionID)) return - const timeoutID = pendingCompactionTimeoutBySession.get(sessionID) - if (timeoutID !== undefined) { - clearTimeout(timeoutID) - pendingCompactionTimeoutBySession.delete(sessionID) - } + clearPendingCompactionTimeout(sessionID) const errorData = autoCompactState.errorDataBySession.get(sessionID) - const lastAssistant = await getLastAssistant(sessionID, ctx.client, ctx.directory) + const lastAssistant = await dependencies.getLastAssistant( + sessionID, + ctx.client, + ctx.directory, + ) const lastAssistantInfo = lastAssistant?.info if (lastAssistantInfo?.summary === true && lastAssistant?.hasContent) { - autoCompactState.pendingCompact.delete(sessionID) + clearSessionState(autoCompactState, sessionID) return } @@ -155,7 +178,7 @@ export function createAnthropicContextWindowLimitRecoveryHook( }) .catch(() => {}) - await executeCompact( + await dependencies.executeCompact( sessionID, { providerID, modelID }, autoCompactState, From 243db8d0f536beee1c2fedced86beed138872046 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Fri, 3 Apr 2026 18:07:47 +0900 Subject: [PATCH 3/3] fix: clear failed empty-content recovery state --- .../summarize-retry-strategy.test.ts | 49 +++++++++++++++++++ .../summarize-retry-strategy.ts | 1 + 2 files changed, 50 insertions(+) diff --git a/src/hooks/anthropic-context-window-limit-recovery/summarize-retry-strategy.test.ts b/src/hooks/anthropic-context-window-limit-recovery/summarize-retry-strategy.test.ts index 7c2e25b69..2c0137c8b 100644 --- a/src/hooks/anthropic-context-window-limit-recovery/summarize-retry-strategy.test.ts +++ b/src/hooks/anthropic-context-window-limit-recovery/summarize-retry-strategy.test.ts @@ -151,4 +151,53 @@ describe("runSummarizeRetryStrategy", () => { //#then expect(autoCompactState.retryStateBySession.has(sessionID)).toBe(false) }) + + test("#given max empty-content recovery attempts reached #when summarize retry exits early #then it clears full recovery state", async () => { + //#given + autoCompactState.pendingCompact.add(sessionID) + autoCompactState.errorDataBySession.set(sessionID, { + currentTokens: 250000, + maxTokens: 200000, + errorType: "non-empty content", + }) + autoCompactState.retryStateBySession.set(sessionID, { + attempt: 1, + lastAttemptTime: Date.now(), + firstAttemptTime: Date.now(), + }) + autoCompactState.truncateStateBySession.set(sessionID, { + truncateAttempt: 2, + }) + autoCompactState.emptyContentAttemptBySession.set(sessionID, 3) + autoCompactState.retryTimerBySession.set( + sessionID, + 1 as unknown as ReturnType, + ) + + //#when + await runSummarizeRetryStrategy({ + sessionID, + msg: { providerID: "anthropic", modelID: "claude-sonnet-4-6" }, + autoCompactState, + client: client as never, + directory, + pluginConfig: {} as OhMyOpenCodeConfig, + errorType: "non-empty content", + }) + + //#then + expect(autoCompactState.pendingCompact.has(sessionID)).toBe(false) + expect(autoCompactState.errorDataBySession.has(sessionID)).toBe(false) + expect(autoCompactState.retryStateBySession.has(sessionID)).toBe(false) + expect(autoCompactState.retryTimerBySession.has(sessionID)).toBe(false) + expect(autoCompactState.truncateStateBySession.has(sessionID)).toBe(false) + expect(autoCompactState.emptyContentAttemptBySession.has(sessionID)).toBe(false) + expect(showToastMock).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.objectContaining({ + title: "Recovery Failed", + }), + }), + ) + }) }) diff --git a/src/hooks/anthropic-context-window-limit-recovery/summarize-retry-strategy.ts b/src/hooks/anthropic-context-window-limit-recovery/summarize-retry-strategy.ts index 2440f699d..f7d527d3c 100644 --- a/src/hooks/anthropic-context-window-limit-recovery/summarize-retry-strategy.ts +++ b/src/hooks/anthropic-context-window-limit-recovery/summarize-retry-strategy.ts @@ -73,6 +73,7 @@ export async function runSummarizeRetryStrategy(params: { return } } else { + clearSessionState(params.autoCompactState, params.sessionID) await params.client.tui .showToast({ body: {