fix: recover from compaction overflow
This commit is contained in:
@@ -29,6 +29,7 @@
|
|||||||
- Models/Providers: treat credential validation failures as auth errors to trigger fallback; normalize `${ENV_VAR}` apiKey values and auto-fill missing provider keys; preserve explicit GitHub Copilot provider config + agent-dir auth profiles.
|
- Models/Providers: treat credential validation failures as auth errors to trigger fallback; normalize `${ENV_VAR}` apiKey values and auto-fill missing provider keys; preserve explicit GitHub Copilot provider config + agent-dir auth profiles.
|
||||||
- Auth: drop invalid auth profiles from ordering so environment keys can still be used for providers like MiniMax.
|
- Auth: drop invalid auth profiles from ordering so environment keys can still be used for providers like MiniMax.
|
||||||
- Gemini: normalize Gemini 3 ids to preview variants; strip Gemini CLI tool call/response ids; downgrade missing `thought_signature`; strip Claude `msg_*` thought_signature fields to avoid base64 decode errors.
|
- Gemini: normalize Gemini 3 ids to preview variants; strip Gemini CLI tool call/response ids; downgrade missing `thought_signature`; strip Claude `msg_*` thought_signature fields to avoid base64 decode errors.
|
||||||
|
- Agents: auto-recover from compaction context overflow by resetting the session and retrying; propagate overflow details from embedded runs so callers can recover.
|
||||||
- MiniMax: strip malformed tool invocation XML; include `MiniMax-VL-01` in implicit provider for image pairing.
|
- MiniMax: strip malformed tool invocation XML; include `MiniMax-VL-01` in implicit provider for image pairing.
|
||||||
- Onboarding/Auth: honor `CLAWDBOT_AGENT_DIR` / `PI_CODING_AGENT_DIR` when writing auth profiles (MiniMax). (#829) — thanks @roshanasingh4.
|
- Onboarding/Auth: honor `CLAWDBOT_AGENT_DIR` / `PI_CODING_AGENT_DIR` when writing auth profiles (MiniMax). (#829) — thanks @roshanasingh4.
|
||||||
- Anthropic: merge consecutive user turns (preserve newest metadata) before validation to avoid incorrect role errors.
|
- Anthropic: merge consecutive user turns (preserve newest metadata) before validation to avoid incorrect role errors.
|
||||||
|
|||||||
@@ -370,8 +370,8 @@ export function formatAssistantErrorText(
|
|||||||
// Check for context overflow (413) errors
|
// Check for context overflow (413) errors
|
||||||
if (isContextOverflowError(raw)) {
|
if (isContextOverflowError(raw)) {
|
||||||
return (
|
return (
|
||||||
"Context overflow: the conversation history is too large. " +
|
"Context overflow: prompt too large for the model. " +
|
||||||
"Use /new or /reset to start a fresh session."
|
"Try again with less input or a larger-context model."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -362,6 +362,10 @@ export type EmbeddedPiRunMeta = {
|
|||||||
durationMs: number;
|
durationMs: number;
|
||||||
agentMeta?: EmbeddedPiAgentMeta;
|
agentMeta?: EmbeddedPiAgentMeta;
|
||||||
aborted?: boolean;
|
aborted?: boolean;
|
||||||
|
error?: {
|
||||||
|
kind: "context_overflow" | "compaction_failure";
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
function buildModelAliasLines(cfg?: ClawdbotConfig) {
|
function buildModelAliasLines(cfg?: ClawdbotConfig) {
|
||||||
@@ -1976,12 +1980,15 @@ export async function runEmbeddedPiAgent(params: {
|
|||||||
if (promptError && !aborted) {
|
if (promptError && !aborted) {
|
||||||
const errorText = describeUnknownError(promptError);
|
const errorText = describeUnknownError(promptError);
|
||||||
if (isContextOverflowError(errorText)) {
|
if (isContextOverflowError(errorText)) {
|
||||||
|
const kind = isCompactionFailureError(errorText)
|
||||||
|
? "compaction_failure"
|
||||||
|
: "context_overflow";
|
||||||
return {
|
return {
|
||||||
payloads: [
|
payloads: [
|
||||||
{
|
{
|
||||||
text:
|
text:
|
||||||
"Context overflow: the conversation history is too large for the model. " +
|
"Context overflow: prompt too large for the model. " +
|
||||||
"Use /new or /reset to start a fresh session, or try a model with a larger context window.",
|
"Try again with less input or a larger-context model.",
|
||||||
isError: true,
|
isError: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -1992,6 +1999,7 @@ export async function runEmbeddedPiAgent(params: {
|
|||||||
provider,
|
provider,
|
||||||
model: model.id,
|
model: model.id,
|
||||||
},
|
},
|
||||||
|
error: { kind, message: errorText },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -525,6 +525,65 @@ describe("runReplyAgent typing (heartbeat)", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("retries after context overflow payload by resetting the session", async () => {
|
||||||
|
const prevStateDir = process.env.CLAWDBOT_STATE_DIR;
|
||||||
|
const stateDir = await fs.mkdtemp(
|
||||||
|
path.join(tmpdir(), "clawdbot-session-overflow-reset-"),
|
||||||
|
);
|
||||||
|
process.env.CLAWDBOT_STATE_DIR = stateDir;
|
||||||
|
try {
|
||||||
|
const sessionId = "session";
|
||||||
|
const storePath = path.join(stateDir, "sessions", "sessions.json");
|
||||||
|
const sessionEntry = { sessionId, updatedAt: Date.now() };
|
||||||
|
const sessionStore = { main: sessionEntry };
|
||||||
|
|
||||||
|
await fs.mkdir(path.dirname(storePath), { recursive: true });
|
||||||
|
await fs.writeFile(storePath, JSON.stringify(sessionStore), "utf-8");
|
||||||
|
|
||||||
|
runEmbeddedPiAgentMock
|
||||||
|
.mockImplementationOnce(async () => ({
|
||||||
|
payloads: [
|
||||||
|
{ text: "Context overflow: prompt too large", isError: true },
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
durationMs: 1,
|
||||||
|
error: {
|
||||||
|
kind: "context_overflow",
|
||||||
|
message:
|
||||||
|
'Context overflow: Summarization failed: 400 {"message":"prompt is too long"}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
.mockImplementationOnce(async () => ({
|
||||||
|
payloads: [{ text: "ok" }],
|
||||||
|
meta: { durationMs: 1 },
|
||||||
|
}));
|
||||||
|
|
||||||
|
const callsBefore = runEmbeddedPiAgentMock.mock.calls.length;
|
||||||
|
const { run } = createMinimalRun({
|
||||||
|
sessionEntry,
|
||||||
|
sessionStore,
|
||||||
|
sessionKey: "main",
|
||||||
|
storePath,
|
||||||
|
});
|
||||||
|
const res = await run();
|
||||||
|
|
||||||
|
expect(runEmbeddedPiAgentMock.mock.calls.length - callsBefore).toBe(2);
|
||||||
|
const payload = Array.isArray(res) ? res[0] : res;
|
||||||
|
expect(payload).toMatchObject({ text: "ok" });
|
||||||
|
expect(sessionStore.main.sessionId).not.toBe(sessionId);
|
||||||
|
|
||||||
|
const persisted = JSON.parse(await fs.readFile(storePath, "utf-8"));
|
||||||
|
expect(persisted.main.sessionId).toBe(sessionStore.main.sessionId);
|
||||||
|
} finally {
|
||||||
|
if (prevStateDir) {
|
||||||
|
process.env.CLAWDBOT_STATE_DIR = prevStateDir;
|
||||||
|
} else {
|
||||||
|
delete process.env.CLAWDBOT_STATE_DIR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("still replies even if session reset fails to persist", async () => {
|
it("still replies even if session reset fails to persist", async () => {
|
||||||
const prevStateDir = process.env.CLAWDBOT_STATE_DIR;
|
const prevStateDir = process.env.CLAWDBOT_STATE_DIR;
|
||||||
const stateDir = await fs.mkdtemp(
|
const stateDir = await fs.mkdtemp(
|
||||||
|
|||||||
@@ -834,6 +834,20 @@ export async function runReplyAgent(params: {
|
|||||||
runResult = fallbackResult.result;
|
runResult = fallbackResult.result;
|
||||||
fallbackProvider = fallbackResult.provider;
|
fallbackProvider = fallbackResult.provider;
|
||||||
fallbackModel = fallbackResult.model;
|
fallbackModel = fallbackResult.model;
|
||||||
|
|
||||||
|
// Some embedded runs surface context overflow as an error payload instead of throwing.
|
||||||
|
// Treat those as a session-level failure and auto-recover by starting a fresh session.
|
||||||
|
const embeddedError = runResult.meta?.error;
|
||||||
|
if (
|
||||||
|
embeddedError &&
|
||||||
|
isContextOverflowError(embeddedError.message) &&
|
||||||
|
!didResetAfterCompactionFailure &&
|
||||||
|
(await resetSessionAfterCompactionFailure(embeddedError.message))
|
||||||
|
) {
|
||||||
|
didResetAfterCompactionFailure = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = err instanceof Error ? err.message : String(err);
|
const message = err instanceof Error ? err.message : String(err);
|
||||||
@@ -894,7 +908,7 @@ export async function runReplyAgent(params: {
|
|||||||
defaultRuntime.error(`Embedded agent failed before reply: ${message}`);
|
defaultRuntime.error(`Embedded agent failed before reply: ${message}`);
|
||||||
return finalizeWithFollowup({
|
return finalizeWithFollowup({
|
||||||
text: isContextOverflow
|
text: isContextOverflow
|
||||||
? "⚠️ Context overflow - conversation too long. Starting fresh might help!"
|
? "⚠️ Context overflow — prompt too large for this model. Try a shorter message or a larger-context model."
|
||||||
: `⚠️ Agent failed before reply: ${message}. Check gateway logs for details.`,
|
: `⚠️ Agent failed before reply: ${message}. Check gateway logs for details.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user