Skip to content

Commit 2076663

Browse files
committed
feat: add tests for handling aborted summaries in agent and compaction engine
1 parent 0497416 commit 2076663

2 files changed

Lines changed: 64 additions & 0 deletions

File tree

‎src/agent/__test__/agent-e2e.test.ts‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,49 @@ describe("agent bundle end-to-end", () => {
245245
expect(firstMessages[0]).toContain("big 0");
246246
});
247247

248+
it("never caches a summary from an aborted run, and resumes with the full history", async () => {
249+
faux.unregister();
250+
faux = registerFauxProvider({
251+
provider: "faux",
252+
models: [{ id: "faux-small", name: "Faux Small", contextWindow: 20_270 }],
253+
});
254+
const firstMessages: string[] = [];
255+
let abortDuringSummary = false;
256+
let summaries = 0;
257+
const opts = {
258+
cwd,
259+
configOverride: { model: faux.getModel(), apiKey: "test-key", source: "explicit" as const },
260+
autoApprove: true,
261+
};
262+
const bundle = createAgent(opts);
263+
faux.setResponses(
264+
Array.from({ length: 80 }, () => (context: Context) => {
265+
if (context.systemPrompt?.startsWith("Summarize the prior conversation")) {
266+
summaries++;
267+
// The user hits Ctrl+C while the summary is being written.
268+
if (abortDuringSummary) bundle.agent.abort();
269+
return fauxAssistantMessage(`summary #${summaries}`);
270+
}
271+
const first = context.messages[0];
272+
firstMessages.push(typeof first?.content === "string" ? first.content : JSON.stringify(first?.content));
273+
return fauxAssistantMessage("ok");
274+
}),
275+
);
276+
for (let i = 0; i < 9; i++) await bundle.agent.prompt(`big ${i} ${"x ".repeat(300)}`);
277+
abortDuringSummary = true;
278+
await bundle.agent.prompt(`big 9 ${"x ".repeat(300)}`).catch(() => undefined);
279+
280+
expect(summaries).toBe(1);
281+
expect(bundle.sessions.load(faux.getModel().id)?.compaction).toBeUndefined();
282+
283+
abortDuringSummary = false;
284+
firstMessages.length = 0;
285+
const resumed = createAgent({ ...opts, resume: true });
286+
await resumed.agent.prompt("after restart");
287+
// A fresh compaction may run after resume, but the aborted summary (#1) is never reused.
288+
expect(firstMessages[0]).not.toContain("summary #1");
289+
});
290+
248291
it("compaction monitor stays inactive when transcript is short", async () => {
249292
faux.setResponses([fauxAssistantMessage("short reply")]);
250293
const bundle = createAgent({

‎src/compaction/engine.test.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,27 @@ describe("CompactionEngine", () => {
237237
expect(result.details.summaryFailed).toBe(true);
238238
});
239239

240+
it("treats a summary from an aborted request as failed", async () => {
241+
const controller = new AbortController();
242+
const glue = {
243+
fast: vi.fn(async () => ""),
244+
smart: vi.fn(async () => {
245+
controller.abort();
246+
return "partial summary";
247+
}),
248+
} as unknown as GlueClient;
249+
const engine = new CompactionEngine({ glue, modelId: "claude-sonnet-4-6", keepRecent: 1 });
250+
const messages = [
251+
userMessage("a"),
252+
assistantMessage("b"),
253+
userMessage("c"),
254+
assistantMessage("d"),
255+
userMessage("e"),
256+
];
257+
const result = await engine.compact(messages, controller.signal);
258+
expect(result.details.summaryFailed).toBe(true);
259+
});
260+
240261
it("marks a successful summary as not failed", async () => {
241262
const engine = new CompactionEngine({ glue: fakeGlue("summary"), modelId: "claude-sonnet-4-6", keepRecent: 1 });
242263
const messages = [

0 commit comments

Comments
 (0)