add branch/compact retries to agent-harness

This commit is contained in:
David Brailovsky
2026-07-21 12:11:39 +00:00
parent 8e53e0e49c
commit 162179af5a
10 changed files with 354 additions and 67 deletions
@@ -164,7 +164,7 @@ export type AgentSessionEvent =
| { type: "auto_retry_start"; attempt: number; maxAttempts: number; delayMs: number; errorMessage: string }
| { type: "auto_retry_end"; success: boolean; attempt: number; finalError?: string }
| {
type: "summarization_retry_start";
type: "summarization_retry_scheduled";
attempt: number;
maxAttempts: number;
delayMs: number;
@@ -176,7 +176,7 @@ export type AgentSessionEvent =
source: "compaction";
reason: "manual" | "threshold" | "overflow";
}
| { type: "summarization_retry_end" };
| { type: "summarization_retry_finished" };
/** Listener function for agent session events */
export type AgentSessionEventListener = (event: AgentSessionEvent) => void;
@@ -2649,9 +2649,9 @@ export class AgentSession {
source: { source: "branchSummary" } | { source: "compaction"; reason: "manual" | "threshold" | "overflow" },
): RetryCallbacks {
return {
onRetry: (attempt, maxAttempts, delayMs, errorMessage) => {
onRetryScheduled: (attempt, maxAttempts, delayMs, errorMessage) => {
this._emit({
type: "summarization_retry_start",
type: "summarization_retry_scheduled",
attempt,
maxAttempts,
delayMs,
@@ -2664,8 +2664,8 @@ export class AgentSession {
...source,
});
},
onRetryEnd: () => {
this._emit({ type: "summarization_retry_end" });
onRetryFinished: () => {
this._emit({ type: "summarization_retry_finished" });
},
};
}
@@ -3111,7 +3111,7 @@ export class InteractiveMode {
break;
}
case "summarization_retry_start": {
case "summarization_retry_scheduled": {
this.showError(event.errorMessage);
this.showStatusIndicator(
new RetryStatusIndicator(this.ui, event.attempt, event.maxAttempts, event.delayMs),
@@ -3131,7 +3131,7 @@ export class InteractiveMode {
break;
}
case "summarization_retry_end": {
case "summarization_retry_finished": {
this.clearStatusIndicator("retry");
this.ui.requestRender();
break;
@@ -100,13 +100,13 @@ describe("#6647 compaction retries transient summarization failures", () => {
expect(result.summary).toContain("recovered summary");
expect(getCallCount()).toBe(3); // 1 initial + 2 retries
const starts = harness.eventsOfType("summarization_retry_start");
const ends = harness.eventsOfType("summarization_retry_end");
const starts = harness.eventsOfType("summarization_retry_scheduled");
const ends = harness.eventsOfType("summarization_retry_finished");
expect(starts).toHaveLength(2);
expect(ends).toHaveLength(1);
expect(starts[0]).toMatchObject({ attempt: 1, maxAttempts: 3, errorMessage: "terminated" });
expect(starts[1]).toMatchObject({ attempt: 2, maxAttempts: 3 });
expect(ends[0]).toMatchObject({ type: "summarization_retry_end" });
expect(ends[0]).toMatchObject({ type: "summarization_retry_finished" });
// model.* referenced to keep imports honest
expect(model.id).toBeTruthy();
});
@@ -125,7 +125,7 @@ describe("#6647 compaction retries transient summarization failures", () => {
await expect(harness.session.compact()).rejects.toThrow("insufficient_quota");
expect(getCallCount()).toBe(1);
expect(harness.eventsOfType("summarization_retry_start")).toHaveLength(0);
expect(harness.eventsOfType("summarization_retry_scheduled")).toHaveLength(0);
});
it("does not retry when retry is disabled", async () => {
@@ -142,7 +142,7 @@ describe("#6647 compaction retries transient summarization failures", () => {
await expect(harness.session.compact()).rejects.toThrow("terminated");
expect(getCallCount()).toBe(1);
expect(harness.eventsOfType("summarization_retry_start")).toHaveLength(0);
expect(harness.eventsOfType("summarization_retry_scheduled")).toHaveLength(0);
});
it("stops retrying after maxRetries and reports failure", async () => {
@@ -159,11 +159,11 @@ describe("#6647 compaction retries transient summarization failures", () => {
await expect(harness.session.compact()).rejects.toThrow("terminated");
expect(getCallCount()).toBe(3); // 1 initial + 2 retries
const starts = harness.eventsOfType("summarization_retry_start");
const ends = harness.eventsOfType("summarization_retry_end");
const starts = harness.eventsOfType("summarization_retry_scheduled");
const ends = harness.eventsOfType("summarization_retry_finished");
expect(starts).toHaveLength(2);
expect(ends).toHaveLength(1);
expect(ends[0]).toMatchObject({ type: "summarization_retry_end" });
expect(ends[0]).toMatchObject({ type: "summarization_retry_finished" });
});
it("aborts an in-flight retry backoff via abortCompaction", async () => {
@@ -183,8 +183,8 @@ describe("#6647 compaction retries transient summarization failures", () => {
await new Promise((resolve) => setTimeout(resolve, 0));
harness.session.abortCompaction();
// The aborted retry backoff rejects with an AbortError (matching the real SDK
// abort path), which compaction classifies as aborted.
// The aborted retry backoff is normalized to an aborted assistant message,
// which compaction classifies as aborted.
await expect(compactPromise).rejects.toThrow();
const compactionEnd = harness.eventsOfType("compaction_end").at(-1);
expect(compactionEnd).toMatchObject({ aborted: true });