fix(session): dropped recovered overflow error turns
Removed recoverable context-overflow and incomplete-response assistant errors from both active context and persisted session history before compaction/promotion schedules the retry. Fixes #3747
This commit is contained in:
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed recoverable context-overflow compaction keeping the failed assistant error turn in visible session history after scheduling the retry. ([#3747](https://github.com/can1357/oh-my-pi/issues/3747))
|
||||
|
||||
## [16.2.4] - 2026-06-28
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -9482,12 +9482,7 @@ export class AgentSession {
|
||||
const errorIsFromBeforeCompaction =
|
||||
compactionEntry !== null && assistantMessage.timestamp < new Date(compactionEntry.timestamp).getTime();
|
||||
if (sameModel && !errorIsFromBeforeCompaction && AIError.isContextOverflow(assistantMessage, contextWindow)) {
|
||||
// Remove the error message from agent state (it IS saved to session for history,
|
||||
// but we don't want it in context for the retry)
|
||||
const messages = this.agent.state.messages;
|
||||
if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
|
||||
this.agent.replaceMessages(messages.slice(0, -1));
|
||||
}
|
||||
await this.#discardRecoverableAssistantTurn(assistantMessage);
|
||||
|
||||
// Try context promotion first - switch to a larger model and retry without compacting
|
||||
const promoted = await this.#tryContextPromotion(assistantMessage);
|
||||
@@ -9512,10 +9507,7 @@ export class AgentSession {
|
||||
// otherwise compaction/handoff. Unlike overflow, the *input* is fine, so we
|
||||
// allow the handoff strategy to actually run.
|
||||
if (sameModel && !errorIsFromBeforeCompaction && assistantMessage.stopReason === "length") {
|
||||
const messages = this.agent.state.messages;
|
||||
if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
|
||||
this.agent.replaceMessages(messages.slice(0, -1));
|
||||
}
|
||||
await this.#discardRecoverableAssistantTurn(assistantMessage);
|
||||
|
||||
const promoted = await this.#tryContextPromotion(assistantMessage);
|
||||
if (promoted) {
|
||||
@@ -9797,6 +9789,11 @@ export class AgentSession {
|
||||
}
|
||||
}
|
||||
|
||||
async #discardRecoverableAssistantTurn(assistantMessage: AssistantMessage): Promise<void> {
|
||||
await this.#waitForSessionMessagePersistence(assistantMessage);
|
||||
this.#discardAssistantTurn(assistantMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop an assistant turn from BOTH the live agent context and the persisted
|
||||
* session branch (reparenting the leaf to the turn's parent), so a discarded
|
||||
|
||||
@@ -382,6 +382,37 @@ describe("AgentSession auto-compaction progress guard", () => {
|
||||
expect(noProgress.length).toBe(0);
|
||||
});
|
||||
|
||||
it("removes the visible overflow error before retrying after compaction", async () => {
|
||||
seedPriorTurns();
|
||||
const continueSpy = vi.spyOn(session.agent, "continue").mockResolvedValue();
|
||||
vi.spyOn(session.agent, "prompt").mockResolvedValue(undefined as never);
|
||||
vi.spyOn(session, "getContextUsage").mockReturnValue({ tokens: 150000, contextWindow: 200000, percent: 75 });
|
||||
|
||||
const { promise: compactionDone, resolve: onCompactionDone } = Promise.withResolvers<void>();
|
||||
session.subscribe(event => {
|
||||
if (event.type === "auto_compaction_end") onCompactionDone();
|
||||
});
|
||||
|
||||
const assistantMsg = overflowAssistant();
|
||||
session.agent.emitExternalEvent({ type: "message_end", message: assistantMsg });
|
||||
session.agent.emitExternalEvent({ type: "agent_end", messages: [assistantMsg] });
|
||||
|
||||
await compactionDone;
|
||||
await session.waitForIdle();
|
||||
|
||||
expect(continueSpy).toHaveBeenCalledTimes(1);
|
||||
expect(sessionManager.getBranch()).not.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: "message",
|
||||
message: expect.objectContaining({
|
||||
role: "assistant",
|
||||
stopReason: "error",
|
||||
errorMessage: assistantMsg.errorMessage,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("retries a small-window overflow when the default reserve exceeds the model window", async () => {
|
||||
// Bundled 4k/8k models can be smaller than the default absolute reserve
|
||||
// (16,384). Retry fit must clamp that reserve; otherwise the budget goes
|
||||
|
||||
Reference in New Issue
Block a user