fix(anthropic-context-window-limit-recovery): add revert fallback when truncation insufficient

When over token limit after truncation, use session.revert to remove last message instead of attempting summarize (which would also fail). Skip summarize entirely when still over limit to prevent infinite loop.

🤖 Generated with assistance of [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
YeonGyu-Kim
2026-01-01 16:41:11 +09:00
parent 2948d94a3c
commit a5983f1678

View File

@@ -158,6 +158,28 @@ export async function getLastAssistant(
}
}
async function getLastMessageId(
sessionID: string,
client: Client,
directory: string,
): Promise<string | null> {
try {
const resp = await client.session.messages({
path: { id: sessionID },
query: { directory },
});
const data = (resp as { data?: unknown[] }).data;
if (!Array.isArray(data) || data.length === 0) return null;
const lastMsg = data[data.length - 1] as Record<string, unknown>;
const info = lastMsg.info as Record<string, unknown> | undefined;
return (info?.id as string) ?? null;
} catch {
return null;
}
}
function clearSessionState(
autoCompactState: AutoCompactState,
sessionID: string,
@@ -399,7 +421,6 @@ export async function executeCompact(
log("[auto-compact] aggressive truncation completed", aggressiveResult);
// If truncation was sufficient, try to continue without summarize
if (aggressiveResult.sufficient) {
clearSessionState(autoCompactState, sessionID);
setTimeout(async () => {
@@ -413,13 +434,81 @@ export async function executeCompact(
}, 500);
return;
}
// If not sufficient, fall through to PHASE 3 (summarize)
} else {
log("[auto-compact] no tool outputs found to truncate", { sessionID });
}
// PHASE 2.5: Revert fallback - if still over limit, remove last message
log("[auto-compact] PHASE 2.5: revert fallback - still over limit after truncation", {
sessionID,
currentTokens: errorData.currentTokens,
maxTokens: errorData.maxTokens,
});
const lastMessageId = await getLastMessageId(
sessionID,
client as Client,
directory,
);
if (lastMessageId) {
try {
await (client as Client).session.revert({
path: { id: sessionID },
body: { messageID: lastMessageId },
query: { directory },
});
await (client as Client).tui
.showToast({
body: {
title: "Message Reverted",
message: "Removed last message to reduce context. Retrying...",
variant: "warning",
duration: 3000,
},
})
.catch(() => {});
clearSessionState(autoCompactState, sessionID);
setTimeout(async () => {
try {
await (client as Client).session.prompt_async({
path: { sessionID },
body: { parts: [{ type: "text", text: "Continue" }] },
query: { directory },
});
} catch {}
}, 500);
return;
} catch (revertError) {
log("[auto-compact] revert failed", { error: String(revertError) });
}
}
}
// PHASE 3: Summarize - last resort after DCP and truncation
// PHASE 3: Summarize - only when under limit (otherwise it will also fail)
if (isOverLimit) {
log("[auto-compact] skipping summarize - still over token limit", {
sessionID,
currentTokens: errorData?.currentTokens,
maxTokens: errorData?.maxTokens,
});
clearSessionState(autoCompactState, sessionID);
await (client as Client).tui
.showToast({
body: {
title: "Recovery Failed",
message: `Still over token limit (${errorData?.currentTokens}/${errorData?.maxTokens}). Please start a new session or manually compact.`,
variant: "error",
duration: 10000,
},
})
.catch(() => {});
return;
}
const retryState = getOrCreateRetryState(autoCompactState, sessionID);