feat(FN-1877): add auto-compaction on context window errors to promptWithFallback

- Detect context-window-limit errors from AI sessions and automatically compact
  the session conversation history before retrying (one attempt per session)
- Centralize auto-compaction in promptWithFallback (pi.ts) so executor, merger,
  and step-session-executor all benefit from the same mechanism
- Remove scattered context-limit error handling from executor.ts, merger.ts, and
  step-session-executor.ts in favor of the centralized approach
- Add comprehensive tests for auto-compaction retry behavior in pi.test.ts
- Remove unused compactSessionContext import from step-session-executor.ts
- Add memory note documenting the centralized auto-compaction design
This commit is contained in:
Fusion
2026-04-15 10:42:20 -07:00
committed by gsxdsm
parent 0c948f5074
commit 7ef3737378
7 changed files with 335 additions and 244 deletions

View File

@@ -3416,12 +3416,9 @@ describe("aiMergeTask — context limit recovery with truncation", () => {
});
}
it("retries with minimal prompt when context limit hit and compaction returns null", async () => {
const { compactSessionContext } = await import("./pi.js");
it("retries with minimal prompt when context limit hit after auto-compaction", async () => {
const { isContextLimitError } = await import("./context-limit-detector.js");
// Mock compaction to return null (fresh session)
vi.mocked(compactSessionContext).mockResolvedValue(null);
vi.mocked(isContextLimitError).mockReturnValue(true);
// Track prompt calls
@@ -3464,16 +3461,12 @@ describe("aiMergeTask — context limit recovery with truncation", () => {
// Second call should have the minimal placeholder
expect(promptCalls[1]).toContain("(see git log)");
// Compaction was attempted
expect(vi.mocked(compactSessionContext)).toHaveBeenCalled();
// Note: Compaction is now handled by promptWithFallback, not by the merger directly
});
it("throws when truncated retry also fails with context limit", async () => {
const { compactSessionContext } = await import("./pi.js");
const { isContextLimitError } = await import("./context-limit-detector.js");
// Mock compaction to return null (fresh session)
vi.mocked(compactSessionContext).mockResolvedValue(null);
vi.mocked(isContextLimitError).mockReturnValue(true);
// Track prompt calls to verify both original and truncated prompts were tried
@@ -3530,16 +3523,12 @@ describe("aiMergeTask — context limit recovery with truncation", () => {
// With 3 merge attempts, this means we should have at least 6 prompt calls total
expect(promptCalls.length).toBeGreaterThan(0);
// Compaction was attempted
expect(vi.mocked(compactSessionContext)).toHaveBeenCalled();
// Note: Compaction is now handled by promptWithFallback, not by the merger directly
});
it("uses normal flow when compaction succeeds (regression test)", async () => {
const { compactSessionContext } = await import("./pi.js");
it("succeeds when prompt succeeds on retry after context error", async () => {
const { isContextLimitError } = await import("./context-limit-detector.js");
// Mock compaction to succeed
vi.mocked(compactSessionContext).mockResolvedValue({ summary: "compacted", tokensBefore: 10000 });
vi.mocked(isContextLimitError).mockReturnValue(true);
// Track prompt calls
@@ -3569,14 +3558,13 @@ describe("aiMergeTask — context limit recovery with truncation", () => {
const result = await aiMergeTask(store, "/tmp/root", "FN-050");
// Merge should succeed after compaction retry
// Merge should succeed after retry
expect(result.merged).toBe(true);
// Should have made 2 prompt calls
expect(promptCalls).toHaveLength(2);
// Compaction was attempted and succeeded
expect(vi.mocked(compactSessionContext)).toHaveBeenCalled();
// Note: Compaction is now handled by promptWithFallback, not by the merger directly
});
it("does not attempt truncation retry for non-context errors", async () => {