fix(coding-agent): unify compaction UI events closes #2617
This commit is contained in:
@@ -153,10 +153,10 @@ describe("AgentSession auto-compaction queue resume", () => {
|
||||
)
|
||||
.mockResolvedValue();
|
||||
|
||||
const events: Array<{ type: string; errorMessage?: string }> = [];
|
||||
const events: Array<{ type: string; reason: string; errorMessage?: string }> = [];
|
||||
session.subscribe((event) => {
|
||||
if (event.type === "auto_compaction_end") {
|
||||
events.push({ type: event.type, errorMessage: event.errorMessage });
|
||||
if (event.type === "compaction_end") {
|
||||
events.push({ type: event.type, reason: event.reason, errorMessage: event.errorMessage });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -171,7 +171,8 @@ describe("AgentSession auto-compaction queue resume", () => {
|
||||
|
||||
expect(runAutoCompactionSpy).toHaveBeenCalledTimes(1);
|
||||
expect(events).toContainEqual({
|
||||
type: "auto_compaction_end",
|
||||
type: "compaction_end",
|
||||
reason: "overflow",
|
||||
errorMessage:
|
||||
"Context overflow recovery failed after one compact-and-retry attempt. Try reducing context or switching to a larger-context model.",
|
||||
});
|
||||
|
||||
@@ -181,7 +181,7 @@ describe.skipIf(!API_KEY)("AgentSession compaction e2e", () => {
|
||||
expect(compactionEntries.length).toBe(1);
|
||||
}, 120000);
|
||||
|
||||
it("should emit correct events during auto-compaction", async () => {
|
||||
it("should emit compaction events during manual compaction", async () => {
|
||||
createSession();
|
||||
|
||||
// Build some history
|
||||
@@ -191,12 +191,15 @@ describe.skipIf(!API_KEY)("AgentSession compaction e2e", () => {
|
||||
// Manually trigger compaction and check events
|
||||
await session.compact();
|
||||
|
||||
// Check that no auto_compaction events were emitted for manual compaction
|
||||
const autoCompactionEvents = events.filter(
|
||||
(e) => e.type === "auto_compaction_start" || e.type === "auto_compaction_end",
|
||||
);
|
||||
// Manual compaction doesn't emit auto_compaction events
|
||||
expect(autoCompactionEvents.length).toBe(0);
|
||||
const compactionEvents = events.filter((e) => e.type === "compaction_start" || e.type === "compaction_end");
|
||||
expect(compactionEvents).toHaveLength(2);
|
||||
expect(compactionEvents[0]).toEqual({ type: "compaction_start", reason: "manual" });
|
||||
expect(compactionEvents[1]).toMatchObject({
|
||||
type: "compaction_end",
|
||||
reason: "manual",
|
||||
aborted: false,
|
||||
willRetry: false,
|
||||
});
|
||||
|
||||
// Regular events should have been emitted
|
||||
const messageEndEvents = events.filter((e) => e.type === "message_end");
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
import { InteractiveMode } from "../src/modes/interactive/interactive-mode.js";
|
||||
|
||||
describe("InteractiveMode compaction events", () => {
|
||||
test("rebuilds chat without appending a duplicate compaction summary", async () => {
|
||||
const fakeThis = {
|
||||
isInitialized: true,
|
||||
footer: { invalidate: vi.fn() },
|
||||
autoCompactionEscapeHandler: undefined as (() => void) | undefined,
|
||||
autoCompactionLoader: undefined,
|
||||
defaultEditor: {},
|
||||
statusContainer: { clear: vi.fn() },
|
||||
chatContainer: { clear: vi.fn() },
|
||||
rebuildChatFromMessages: vi.fn(),
|
||||
addMessageToChat: vi.fn(),
|
||||
showError: vi.fn(),
|
||||
showStatus: vi.fn(),
|
||||
flushCompactionQueue: vi.fn().mockResolvedValue(undefined),
|
||||
ui: { requestRender: vi.fn() },
|
||||
};
|
||||
|
||||
const handleEvent = Reflect.get(InteractiveMode.prototype, "handleEvent") as (
|
||||
this: typeof fakeThis,
|
||||
event: {
|
||||
type: "compaction_end";
|
||||
reason: "manual" | "threshold" | "overflow";
|
||||
result: { tokensBefore: number; summary: string } | undefined;
|
||||
aborted: boolean;
|
||||
willRetry: boolean;
|
||||
errorMessage?: string;
|
||||
},
|
||||
) => Promise<void>;
|
||||
|
||||
await handleEvent.call(fakeThis, {
|
||||
type: "compaction_end",
|
||||
reason: "manual",
|
||||
result: {
|
||||
tokensBefore: 123,
|
||||
summary: "summary",
|
||||
},
|
||||
aborted: false,
|
||||
willRetry: false,
|
||||
});
|
||||
|
||||
expect(fakeThis.chatContainer.clear).toHaveBeenCalledTimes(1);
|
||||
expect(fakeThis.rebuildChatFromMessages).toHaveBeenCalledTimes(1);
|
||||
expect(fakeThis.addMessageToChat).not.toHaveBeenCalled();
|
||||
expect(fakeThis.flushCompactionQueue).toHaveBeenCalledWith({ willRetry: false });
|
||||
});
|
||||
});
|
||||
56
packages/coding-agent/test/trigger-compact-extension.test.ts
Normal file
56
packages/coding-agent/test/trigger-compact-extension.test.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
import triggerCompactExtension from "../examples/extensions/trigger-compact.js";
|
||||
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "../src/core/extensions/index.js";
|
||||
|
||||
function createContext(tokens: number | null, compact = vi.fn()): ExtensionContext {
|
||||
return {
|
||||
hasUI: false,
|
||||
ui: {} as ExtensionContext["ui"],
|
||||
cwd: process.cwd(),
|
||||
sessionManager: {} as ExtensionContext["sessionManager"],
|
||||
modelRegistry: {} as ExtensionContext["modelRegistry"],
|
||||
model: undefined,
|
||||
isIdle: () => true,
|
||||
abort: vi.fn(),
|
||||
hasPendingMessages: () => false,
|
||||
shutdown: vi.fn(),
|
||||
getContextUsage: () => ({ tokens, contextWindow: 200_000, percent: tokens === null ? null : tokens / 2000 }),
|
||||
compact,
|
||||
getSystemPrompt: () => "",
|
||||
};
|
||||
}
|
||||
|
||||
describe("trigger-compact example extension", () => {
|
||||
test("only auto-compacts when context usage crosses the threshold", () => {
|
||||
let turnEndHandler:
|
||||
| ((event: { type: "turn_end" }, ctx: ExtensionContext | ExtensionCommandContext) => void)
|
||||
| undefined;
|
||||
|
||||
const api = {
|
||||
on: (event: string, handler: (event: { type: "turn_end" }, ctx: ExtensionContext) => void) => {
|
||||
if (event === "turn_end") {
|
||||
turnEndHandler = handler;
|
||||
}
|
||||
},
|
||||
registerCommand: vi.fn(),
|
||||
} as unknown as ExtensionAPI;
|
||||
|
||||
triggerCompactExtension(api);
|
||||
expect(turnEndHandler).toBeDefined();
|
||||
|
||||
const compact = vi.fn();
|
||||
const event = { type: "turn_end" } as const;
|
||||
|
||||
turnEndHandler?.(event, createContext(110_000, compact));
|
||||
expect(compact).not.toHaveBeenCalled();
|
||||
|
||||
turnEndHandler?.(event, createContext(120_000, compact));
|
||||
expect(compact).not.toHaveBeenCalled();
|
||||
|
||||
turnEndHandler?.(event, createContext(95_000, compact));
|
||||
expect(compact).not.toHaveBeenCalled();
|
||||
|
||||
turnEndHandler?.(event, createContext(105_000, compact));
|
||||
expect(compact).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user