fix(core,docs): window-derived output caps and compaction threshold, slower retry ladder, retry-all-but-auth (#235)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Yaowei Zheng
2026-08-07 19:14:26 +08:00
committed by GitHub
parent 169c7d5006
commit 864261eade
20 changed files with 846 additions and 251 deletions
+14 -12
View File
@@ -1910,14 +1910,15 @@ describe("ContextEngine LLM timeout / network interruption (PRN-012)", () => {
expect((abort!.payload as { reason?: string }).reason).toContain("llm request error");
});
it("a quota-403 (classified timeout) retries within the default cap and succeeds", async () => {
it("a bare 403 (classified failed) retries within the default cap and succeeds", async () => {
let calls = 0;
const llm: LLMInterface = {
async *streamGenerate() {
calls += 1;
// Two quota rejections (GenerativeModel classifies them as timeout), then success —
// attempt 3 is within the default cap of 5.
if (calls <= 2) return { status: "timeout" };
// Two provider 403 rejections (GenerativeModel labels them failed — there is no
// quota/message heuristic anymore), then success: every non-auth failure rides
// the ladder, and attempt 3 is within the default cap of 5.
if (calls <= 2) return { status: "failed", errorMessage: "403 forbidden" };
yield assistantText("recovered");
yield tokenUsage(emptyTokenCounts(), {
cache_read: 0,
@@ -2020,15 +2021,16 @@ describe("ContextEngine LLM timeout / network interruption (PRN-012)", () => {
expect(engine.skipReconnectWait()).toBe(false);
});
it("reconnectDelayMs: exponential-with-ceiling ladder (defaults: 250ms base, 30s cap)", () => {
// The default cap (5) walks the first five steps — 250+500+1000+2000+4000 ≈ 7.75s of
// total patience; the formula keeps climbing to the 30s ceiling for larger caps.
const ladder = [1, 2, 3, 4, 5].map((n) => reconnectDelayMs(250, 30_000, n));
expect(ladder).toEqual([250, 500, 1000, 2000, 4000]);
expect(ladder.reduce((a, b) => a + b, 0)).toBe(7750);
expect([6, 7, 8].map((n) => reconnectDelayMs(250, 30_000, n))).toEqual([8000, 16000, 30000]);
it("reconnectDelayMs: exponential-with-ceiling ladder (defaults: 2s base, 30s cap)", () => {
// The default cap (5) walks 2s/4s/8s/16s and hits the 30s ceiling on the fifth wait —
// ≈ 60s of total patience (issue #218: the old 250ms base burned the whole ladder in
// ~7.75s, faster than a provider restart or rate-limit window can recover). Every wait
// sits at or above the hosts' 2s countdown floor, so each retry is visible.
const ladder = [1, 2, 3, 4, 5].map((n) => reconnectDelayMs(2000, 30_000, n));
expect(ladder).toEqual([2000, 4000, 8000, 16000, 30000]);
expect(ladder.reduce((a, b) => a + b, 0)).toBe(60_000);
// Past the ceiling the delay stays pinned (no overflow, no further growth).
expect(reconnectDelayMs(250, 30_000, 12)).toBe(30_000);
expect([6, 7].map((n) => reconnectDelayMs(2000, 30_000, n))).toEqual([30_000, 30_000]);
// The cap also applies when the base itself exceeds it.
expect(reconnectDelayMs(50_000, 30_000, 1)).toBe(30_000);
});