fix: auto-recover from Gemini session corruption

Detect the Gemini API error 'function call turn comes immediately after
a user turn or after a function response turn' which indicates corrupted
session history.

When detected:
- Delete the corrupted transcript file
- Remove the session entry from the store
- Return a user-friendly message asking them to retry

This prevents the error loop where every subsequent message fails with
the same error until manual intervention.

Fixes #296
This commit is contained in:
VAC
2026-01-06 07:24:51 -05:00
parent 77ac45b90e
commit eadb923000

View File

@@ -1,4 +1,5 @@
import crypto from "node:crypto";
import fs from "node:fs";
import { lookupContextTokens } from "../../agents/context.js";
import { DEFAULT_CONTEXT_TOKENS } from "../../agents/defaults.js";
import { runWithModelFallback } from "../../agents/model-fallback.js";
@@ -8,6 +9,7 @@ import {
} from "../../agents/pi-embedded.js";
import {
loadSessionStore,
resolveSessionTranscriptPath,
type SessionEntry,
saveSessionStore,
} from "../../config/sessions.js";
@@ -346,6 +348,37 @@ export async function runReplyAgent(params: {
const message = err instanceof Error ? err.message : String(err);
const isContextOverflow =
/context.*overflow|too large|context window/i.test(message);
const isSessionCorruption =
/function call turn comes immediately after|INVALID_ARGUMENT.*function/i.test(
message,
);
// Auto-recover from Gemini session corruption by resetting the session
if (isSessionCorruption && sessionKey && sessionStore && storePath) {
const corruptedSessionId = sessionEntry?.sessionId;
defaultRuntime.error(
`Session history corrupted (Gemini function call ordering). Resetting session: ${sessionKey}`,
);
// Delete transcript file if it exists
if (corruptedSessionId) {
const transcriptPath = resolveSessionTranscriptPath(corruptedSessionId);
try {
fs.unlinkSync(transcriptPath);
} catch {
// Ignore if file doesn't exist
}
}
// Remove session entry from store
delete sessionStore[sessionKey];
await saveSessionStore(storePath, sessionStore);
return finalizeWithFollowup({
text: "⚠️ Session history was corrupted. I've reset the conversation - please try again!",
});
}
defaultRuntime.error(`Embedded agent failed before reply: ${message}`);
return finalizeWithFollowup({
text: isContextOverflow