Files
oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/summarize-retry-strategy.ts
T
YeonGyu-Kim 69d0b23ab6 fix(anthropic-recovery): clear session state after successful summarize and fix timing test
- Add missing clearSessionState() call after successful summarize (line 117)
  Without this, retry state persisted even after success, potentially causing
  unnecessary retries on subsequent compaction events.

- Fix timing-sensitive test: adjust attempt=0 and firstAttemptTime to give
  proper remainingTimeMs buffer for capped delay calculation.

Fixes #2225
Supersedes #2671
2026-03-24 16:23:11 +09:00

172 lines
5.4 KiB
TypeScript

import type { AutoCompactState } from "./types"
import type { OhMyOpenCodeConfig } from "../../config"
import { RETRY_CONFIG } from "./types"
import type { Client } from "./client"
import { clearSessionState, getEmptyContentAttempt, getOrCreateRetryState } from "./state"
import { sanitizeEmptyMessagesBeforeSummarize } from "./message-builder"
import { fixEmptyMessages } from "./empty-content-recovery"
import { resolveCompactionModel } from "../shared/compaction-model-resolver"
const SUMMARIZE_RETRY_TOTAL_TIMEOUT_MS = 120_000
export async function runSummarizeRetryStrategy(params: {
sessionID: string
msg: Record<string, unknown>
autoCompactState: AutoCompactState
client: Client
directory: string
pluginConfig: OhMyOpenCodeConfig
errorType?: string
messageIndex?: number
}): Promise<void> {
const retryState = getOrCreateRetryState(params.autoCompactState, params.sessionID)
const now = Date.now()
if (retryState.firstAttemptTime === 0) {
retryState.firstAttemptTime = now
}
const elapsedTimeMs = now - retryState.firstAttemptTime
if (elapsedTimeMs >= SUMMARIZE_RETRY_TOTAL_TIMEOUT_MS) {
clearSessionState(params.autoCompactState, params.sessionID)
await params.client.tui
.showToast({
body: {
title: "Auto Compact Timed Out",
message: "Compaction retries exceeded the timeout window. Please start a new session.",
variant: "error",
duration: 5000,
},
})
.catch(() => {})
return
}
if (params.errorType?.includes("non-empty content")) {
const attempt = getEmptyContentAttempt(params.autoCompactState, params.sessionID)
if (attempt < 3) {
const fixed = await fixEmptyMessages({
sessionID: params.sessionID,
autoCompactState: params.autoCompactState,
client: params.client,
messageIndex: params.messageIndex,
})
if (fixed) {
setTimeout(() => {
void runSummarizeRetryStrategy(params)
}, 500)
return
}
} else {
await params.client.tui
.showToast({
body: {
title: "Recovery Failed",
message:
"Max recovery attempts (3) reached for empty content error. Please start a new session.",
variant: "error",
duration: 10000,
},
})
.catch(() => {})
return
}
}
if (Date.now() - retryState.lastAttemptTime > 300000) {
retryState.attempt = 0
retryState.firstAttemptTime = Date.now()
params.autoCompactState.truncateStateBySession.delete(params.sessionID)
}
if (retryState.attempt < RETRY_CONFIG.maxAttempts) {
retryState.attempt++
retryState.lastAttemptTime = Date.now()
const providerID = params.msg.providerID as string | undefined
const modelID = params.msg.modelID as string | undefined
if (providerID && modelID) {
try {
await sanitizeEmptyMessagesBeforeSummarize(params.sessionID, params.client)
await params.client.tui
.showToast({
body: {
title: "Auto Compact",
message: `Summarizing session (attempt ${retryState.attempt}/${RETRY_CONFIG.maxAttempts})...`,
variant: "warning",
duration: 3000,
},
})
.catch(() => {})
const { providerID: targetProviderID, modelID: targetModelID } = resolveCompactionModel(
params.pluginConfig,
params.sessionID,
providerID,
modelID
)
const summarizeBody = { providerID: targetProviderID, modelID: targetModelID, auto: true }
await params.client.session.summarize({
path: { id: params.sessionID },
body: summarizeBody as never,
query: { directory: params.directory },
})
clearSessionState(params.autoCompactState, params.sessionID)
return
} catch {
const remainingTimeMs = SUMMARIZE_RETRY_TOTAL_TIMEOUT_MS - (Date.now() - retryState.firstAttemptTime)
if (remainingTimeMs <= 0) {
clearSessionState(params.autoCompactState, params.sessionID)
await params.client.tui
.showToast({
body: {
title: "Auto Compact Timed Out",
message: "Compaction retries exceeded the timeout window. Please start a new session.",
variant: "error",
duration: 5000,
},
})
.catch(() => {})
return
}
const delay =
RETRY_CONFIG.initialDelayMs *
Math.pow(RETRY_CONFIG.backoffFactor, retryState.attempt - 1)
const cappedDelay = Math.min(delay, RETRY_CONFIG.maxDelayMs, remainingTimeMs)
setTimeout(() => {
void runSummarizeRetryStrategy(params)
}, cappedDelay)
return
}
} else {
await params.client.tui
.showToast({
body: {
title: "Summarize Skipped",
message: "Missing providerID or modelID.",
variant: "warning",
duration: 3000,
},
})
.catch(() => {})
}
}
clearSessionState(params.autoCompactState, params.sessionID)
await params.client.tui
.showToast({
body: {
title: "Auto Compact Failed",
message: "All recovery attempts failed. Please start a new session.",
variant: "error",
duration: 5000,
},
})
.catch(() => {})
}