fix: simplify excluded custom message tests

This commit is contained in:
Armin Ronacher
2026-06-14 22:55:29 +02:00
parent 1edcc76f89
commit 290af80186
7 changed files with 151 additions and 380 deletions
+24 -54
View File
@@ -560,10 +560,16 @@ describe("Agent", () => {
await firstPrompt.catch(() => {});
});
it("continue() should reserve the active run while awaiting async LLM conversion", async () => {
it("continue() should reserve the active run before async LLM conversion and use the filtered context tail", async () => {
const convertStarted = createDeferred();
const releaseConvert = createDeferred();
let convertCallCount = 0;
let providerMessages: Message[] = [];
const displayOnlyMessage = {
role: "displayOnly",
content: "status",
timestamp: Date.now(),
} as unknown as AgentMessage;
const agent = new Agent({
convertToLlm: async (messages) => {
convertCallCount++;
@@ -571,11 +577,14 @@ describe("Agent", () => {
convertStarted.resolve();
await releaseConvert.promise;
}
return messages.filter(
(message) => message.role === "user" || message.role === "assistant" || message.role === "toolResult",
) as Message[];
return messages
.filter((message) => (message as { role: string }).role !== "displayOnly")
.filter(
(message) => message.role === "user" || message.role === "assistant" || message.role === "toolResult",
) as Message[];
},
streamFn: () => {
streamFn: (_model, context) => {
providerMessages = context.messages;
const stream = new MockAssistantStream();
queueMicrotask(() => {
stream.push({ type: "done", reason: "stop", message: createAssistantMessage("Processed") });
@@ -587,9 +596,16 @@ describe("Agent", () => {
{
role: "user",
content: [{ type: "text", text: "Initial" }],
timestamp: Date.now(),
timestamp: Date.now() - 10,
},
createAssistantMessage("Initial response"),
displayOnlyMessage,
];
agent.followUp({
role: "user",
content: [{ type: "text", text: "Queued follow-up" }],
timestamp: Date.now(),
});
const continuePromise = agent.continue();
await convertStarted.promise;
@@ -601,6 +617,8 @@ describe("Agent", () => {
await continuePromise;
expect(convertCallCount).toBe(2);
expect(providerMessages[providerMessages.length - 1]?.role).toBe("user");
expect(agent.state.messages).toContain(displayOnlyMessage);
});
it("continue() should process queued follow-up messages after an assistant turn", async () => {
@@ -641,54 +659,6 @@ describe("Agent", () => {
expect(agent.state.messages[agent.state.messages.length - 1].role).toBe("assistant");
});
it("continue() should process queued follow-up messages after filtered state messages", async () => {
let providerCallCount = 0;
let providerMessages: Message[] = [];
const displayOnlyMessage = {
role: "displayOnly",
content: "status",
timestamp: Date.now(),
} as unknown as AgentMessage;
const agent = new Agent({
convertToLlm: (messages) =>
messages
.filter((message) => (message as { role: string }).role !== "displayOnly")
.filter(
(message) => message.role === "user" || message.role === "assistant" || message.role === "toolResult",
) as Message[],
streamFn: (_model, context) => {
providerCallCount++;
providerMessages = context.messages;
const stream = new MockAssistantStream();
queueMicrotask(() => {
stream.push({ type: "done", reason: "stop", message: createAssistantMessage("Processed") });
});
return stream;
},
});
agent.state.messages = [
{
role: "user",
content: [{ type: "text", text: "Initial" }],
timestamp: Date.now() - 10,
},
createAssistantMessage("Initial response"),
displayOnlyMessage,
];
agent.followUp({
role: "user",
content: [{ type: "text", text: "Queued follow-up" }],
timestamp: Date.now(),
});
await agent.continue();
expect(providerCallCount).toBe(1);
expect(providerMessages[providerMessages.length - 1]?.role).toBe("user");
expect(agent.state.messages).toContain(displayOnlyMessage);
});
it("continue() should keep one-at-a-time steering semantics from assistant tail", async () => {
let responseCount = 0;
const agent = new Agent({
+19 -75
View File
@@ -234,42 +234,6 @@ describe("harness compaction", () => {
isSplitTurn: false,
});
const cutUser = createMessageEntry(createUserMessage("inspect file"));
const cutAssistant = createMessageEntry(
{
...createAssistantMessage("calling tool"),
content: [{ type: "toolCall", id: "call-2", name: "read", arguments: { path: "file.ts" } }],
},
cutUser.id,
);
const cutToolResult = createMessageEntry(
{
role: "toolResult",
toolCallId: "call-2",
toolName: "read",
content: [{ type: "text", text: "x".repeat(1000) }],
isError: false,
timestamp: Date.now(),
},
cutAssistant.id,
);
const excludedCustom: CustomMessageEntry = {
type: "custom_message",
id: createId(),
parentId: cutToolResult.id,
timestamp: new Date().toISOString(),
customType: "status",
content: "tool finished",
display: true,
excludeFromContext: true,
};
const cutAssistantFinal = createMessageEntry(createAssistantMessage("done"), excludedCustom.id);
expect(findCutPoint([cutUser, cutAssistant, cutToolResult, excludedCustom, cutAssistantFinal], 0, 5, 2)).toEqual({
firstKeptEntryIndex: 4,
turnStartIndex: 0,
isSplitTurn: true,
});
const user = createMessageEntry(createUserMessage("user"));
const compaction = createCompactionEntry("summary", user.id, user.id);
const assistant = createMessageEntry(createAssistantMessage("assistant"), compaction.id);
@@ -425,8 +389,8 @@ describe("harness compaction", () => {
expect([...preparation!.fileOps.written]).toContain("written.ts");
});
it("skips excluded custom messages during compaction token estimates", () => {
const customMessage: CustomMessageEntry = {
it("ignores excluded custom messages during compaction and branch-summary preparation", () => {
const excludedCustom: CustomMessageEntry = {
type: "custom_message",
id: createId(),
parentId: null,
@@ -436,26 +400,27 @@ describe("harness compaction", () => {
display: true,
excludeFromContext: true,
};
const user = createMessageEntry(createUserMessage("keep"), customMessage.id);
const user = createMessageEntry(createUserMessage("keep"), excludedCustom.id);
const preparation = getOrThrow(
prepareCompaction([customMessage, user], { enabled: true, reserveTokens: 0, keepRecentTokens: 1 }),
const simplePreparation = getOrThrow(
prepareCompaction([excludedCustom, user], { enabled: true, reserveTokens: 0, keepRecentTokens: 1 }),
);
const branchPreparation = prepareBranchEntries([excludedCustom, user], 10);
expect(preparation?.tokensBefore).toBe(1);
expect(preparation?.messagesToSummarize).toEqual([]);
});
expect(simplePreparation?.tokensBefore).toBe(1);
expect(simplePreparation?.messagesToSummarize).toEqual([]);
expect(branchPreparation.messages.map((message) => message.role)).toEqual(["user"]);
expect(branchPreparation.totalTokens).toBe(1);
it("ignores excluded custom messages when finding split-turn prefixes", () => {
const user = createMessageEntry(createUserMessage("inspect file"));
const splitUser = createMessageEntry(createUserMessage("inspect file"));
const assistantWithToolCall = createMessageEntry(
{
...createAssistantMessage("calling tool"),
content: [{ type: "toolCall", id: "call-1", name: "read", arguments: { path: "file.ts" } }],
},
user.id,
splitUser.id,
);
const customMessage: CustomMessageEntry = {
const splitExcludedCustom: CustomMessageEntry = {
type: "custom_message",
id: createId(),
parentId: assistantWithToolCall.id,
@@ -474,47 +439,26 @@ describe("harness compaction", () => {
isError: false,
timestamp: Date.now(),
},
customMessage.id,
splitExcludedCustom.id,
);
const assistantFinal = createMessageEntry(createAssistantMessage("done"), toolResult.id);
const preparation = getOrThrow(
prepareCompaction([user, assistantWithToolCall, customMessage, toolResult, assistantFinal], {
const splitPreparation = getOrThrow(
prepareCompaction([splitUser, assistantWithToolCall, splitExcludedCustom, toolResult, assistantFinal], {
enabled: true,
reserveTokens: 0,
keepRecentTokens: 1,
}),
);
expect(preparation).toBeDefined();
expect(preparation?.isSplitTurn).toBe(true);
expect(preparation?.firstKeptEntryId).toBe(assistantFinal.id);
expect(preparation?.turnPrefixMessages.map((message) => message.role)).toEqual([
expect(splitPreparation?.isSplitTurn).toBe(true);
expect(splitPreparation?.firstKeptEntryId).toBe(assistantFinal.id);
expect(splitPreparation?.turnPrefixMessages.map((message) => message.role)).toEqual([
"user",
"assistant",
"toolResult",
]);
});
it("skips excluded custom messages before branch summary token budgeting", () => {
const user = createMessageEntry(createUserMessage("keep"));
const customMessage: CustomMessageEntry = {
type: "custom_message",
id: createId(),
parentId: user.id,
timestamp: new Date().toISOString(),
customType: "status",
content: "x".repeat(1000),
display: true,
excludeFromContext: true,
};
const preparation = prepareBranchEntries([user, customMessage], 10);
expect(preparation.messages.map((message) => message.role)).toEqual(["user"]);
expect(preparation.totalTokens).toBe(1);
});
it("prepares custom and branch summary entries for summarization", () => {
const branchSummary: BranchSummaryEntry = {
type: "branch_summary",
@@ -1,47 +0,0 @@
import { describe, expect, it } from "vitest";
import { prepareBranchEntries } from "../src/core/compaction/branch-summarization.ts";
import type { CustomMessageEntry, SessionMessageEntry } from "../src/core/session-manager.ts";
function userEntry(id: string, parentId: string | null, text: string): SessionMessageEntry {
return {
type: "message",
id,
parentId,
timestamp: "2025-01-01T00:00:00Z",
message: {
role: "user",
content: [{ type: "text", text }],
timestamp: 1,
},
};
}
function customEntry(
id: string,
parentId: string | null,
content: string,
excludeFromContext: boolean,
): CustomMessageEntry {
return {
type: "custom_message",
id,
parentId,
timestamp: "2025-01-01T00:00:00Z",
customType: "status",
content,
display: true,
excludeFromContext,
};
}
describe("branch summarization", () => {
it("skips excluded custom messages before token budgeting", () => {
const user = userEntry("user", null, "keep");
const excluded = customEntry("custom", user.id, "x".repeat(1000), true);
const preparation = prepareBranchEntries([user, excluded], 10);
expect(preparation.messages.map((message) => message.role)).toEqual(["user"]);
expect(preparation.totalTokens).toBe(1);
});
});
+24 -66
View File
@@ -4,6 +4,7 @@ import { getModel } from "@earendil-works/pi-ai";
import { readFileSync } from "fs";
import { join } from "path";
import { beforeEach, describe, expect, it } from "vitest";
import { prepareBranchEntries } from "../src/core/compaction/branch-summarization.ts";
import {
type CompactionSettings,
calculateContextTokens,
@@ -15,7 +16,6 @@ import {
prepareCompaction,
shouldCompact,
} from "../src/core/compaction/index.ts";
import type { CustomMessage } from "../src/core/messages.ts";
import {
buildSessionContext,
type CompactionEntry,
@@ -202,27 +202,6 @@ describe("Token calculation", () => {
const usage = createMockUsage(0, 0, 0, 0);
expect(calculateContextTokens(usage)).toBe(0);
});
it("should ignore excluded custom messages in context token estimates", () => {
const excludedCustom: CustomMessage = {
role: "custom",
customType: "status",
content: "x".repeat(1000),
display: true,
excludeFromContext: true,
timestamp: Date.now(),
};
const visibleCustom: CustomMessage = { ...excludedCustom, excludeFromContext: false };
const assistant = createAssistantMessage("assistant", createMockUsage(10, 5));
expect(estimateContextTokens([excludedCustom])).toMatchObject({ tokens: 0, trailingTokens: 0 });
expect(estimateContextTokens([visibleCustom]).tokens).toBeGreaterThan(0);
expect(estimateContextTokens([assistant, excludedCustom])).toMatchObject({
tokens: 15,
usageTokens: 15,
trailingTokens: 0,
});
});
});
describe("getLastAssistantUsage", () => {
@@ -346,31 +325,6 @@ describe("findCutPoint", () => {
expect(result.turnStartIndex).toBe(2); // Turn 2 starts at index 2
}
});
it("should not select excluded custom messages as cut points", () => {
const user = createMessageEntry(createUserMessage("inspect file"));
const assistantWithToolCall = createMessageEntry({
...createAssistantMessage("calling tool"),
content: [{ type: "toolCall", id: "call-1", name: "read", arguments: { path: "file.ts" } }],
});
const toolResult = createMessageEntry({
role: "toolResult",
toolCallId: "call-1",
toolName: "read",
content: [{ type: "text", text: "x".repeat(1000) }],
isError: false,
timestamp: Date.now(),
});
const excludedCustom = createCustomMessageEntry("tool finished", true);
const assistantFinal = createMessageEntry(createAssistantMessage("done"));
const entries = [user, assistantWithToolCall, toolResult, excludedCustom, assistantFinal];
const result = findCutPoint(entries, 0, entries.length, 2);
expect(result.firstKeptEntryIndex).toBe(4);
expect(result.turnStartIndex).toBe(0);
expect(result.isSplitTurn).toBe(true);
});
});
describe("buildSessionContext", () => {
@@ -460,27 +414,29 @@ describe("buildSessionContext", () => {
});
describe("prepareCompaction with custom messages", () => {
it("should ignore excluded custom messages in token estimates and summarized messages", () => {
it("should ignore excluded custom messages in compaction and branch-summary preparation", () => {
const excludedCustom = createCustomMessageEntry("x".repeat(1000), true);
const user = createMessageEntry(createUserMessage("keep"));
const preparation = prepareCompaction([excludedCustom, user], {
const simplePreparation = prepareCompaction([excludedCustom, user], {
enabled: true,
reserveTokens: 0,
keepRecentTokens: 1,
});
const branchPreparation = prepareBranchEntries([excludedCustom, user], 10);
expect(preparation).toBeDefined();
expect(preparation!.tokensBefore).toBe(1);
expect(preparation!.messagesToSummarize).toEqual([]);
});
expect(simplePreparation).toBeDefined();
expect(simplePreparation!.tokensBefore).toBe(1);
expect(simplePreparation!.messagesToSummarize).toEqual([]);
expect(branchPreparation.messages.map((message) => message.role)).toEqual(["user"]);
expect(branchPreparation.totalTokens).toBe(1);
it("should ignore excluded custom messages when finding split-turn prefixes", () => {
const user = createMessageEntry(createUserMessage("inspect file"));
resetEntryCounter();
const splitUser = createMessageEntry(createUserMessage("inspect file"));
const assistantWithToolCall = createMessageEntry({
...createAssistantMessage("calling tool"),
content: [{ type: "toolCall", id: "call-1", name: "read", arguments: { path: "file.ts" } }],
});
const excludedCustom = createCustomMessageEntry("tool is running", true);
const splitExcludedCustom = createCustomMessageEntry("tool is running", true);
const toolResult = createMessageEntry({
role: "toolResult",
toolCallId: "call-1",
@@ -490,17 +446,19 @@ describe("prepareCompaction with custom messages", () => {
timestamp: Date.now(),
});
const assistantFinal = createMessageEntry(createAssistantMessage("done"));
const splitPreparation = prepareCompaction(
[splitUser, assistantWithToolCall, splitExcludedCustom, toolResult, assistantFinal],
{
enabled: true,
reserveTokens: 0,
keepRecentTokens: 1,
},
);
const preparation = prepareCompaction([user, assistantWithToolCall, excludedCustom, toolResult, assistantFinal], {
enabled: true,
reserveTokens: 0,
keepRecentTokens: 1,
});
expect(preparation).toBeDefined();
expect(preparation!.isSplitTurn).toBe(true);
expect(preparation!.firstKeptEntryId).toBe(assistantFinal.id);
expect(preparation!.turnPrefixMessages.map((message) => message.role)).toEqual([
expect(splitPreparation).toBeDefined();
expect(splitPreparation!.isSplitTurn).toBe(true);
expect(splitPreparation!.firstKeptEntryId).toBe(assistantFinal.id);
expect(splitPreparation!.turnPrefixMessages.map((message) => message.role)).toEqual([
"user",
"assistant",
"toolResult",
@@ -145,41 +145,17 @@ describe("AgentSession bash and persistence characterization", () => {
};
const harness = await createHarness({ tools: [echoTool] });
harnesses.push(harness);
let providerUserTexts: string[] = [];
harness.setResponses([
fauxAssistantMessage([fauxToolCall("echo", { text: "hello" })], { stopReason: "toolUse" }),
(context) => {
providerUserTexts = context.messages
.filter((message) => message.role === "user")
.map((message) => getMessageText(message));
return fauxAssistantMessage([fauxToolCall("echo", { text: "hello" })], { stopReason: "toolUse" });
},
fauxAssistantMessage("done"),
]);
await harness.session.sendCustomMessage({
customType: "note",
content: "hello",
display: true,
details: { a: 1 },
});
await harness.session.prompt("start");
const entries = harness.sessionManager.getEntries();
expect(entries.map((entry) => entry.type)).toEqual([
"custom_message",
"message",
"message",
"message",
"message",
]);
expect(harness.session.messages.map((message) => message.role)).toEqual([
"custom",
"user",
"assistant",
"toolResult",
"assistant",
]);
});
it("excludes flagged custom messages from LLM context while preserving them", async () => {
const harness = await createHarness();
harnesses.push(harness);
let userTexts: string[] = [];
await harness.session.sendCustomMessage({
customType: "status",
content: "status panel",
@@ -187,26 +163,37 @@ describe("AgentSession bash and persistence characterization", () => {
details: { a: 1 },
excludeFromContext: true,
});
harness.setResponses([
(context) => {
userTexts = context.messages
.filter((message) => message.role === "user")
.map((message) => getMessageText(message));
return fauxAssistantMessage("done");
},
]);
await harness.session.sendCustomMessage({
customType: "note",
content: "hello",
display: true,
details: { a: 1 },
});
await harness.session.prompt("start");
await harness.session.prompt("next prompt");
expect(userTexts).toEqual(["next prompt"]);
expect(providerUserTexts).toEqual(["hello", "start"]);
const entries = harness.sessionManager.getEntries();
expect(entries[0]?.type).toBe("custom_message");
if (entries[0]?.type !== "custom_message") return;
expect(entries[0].excludeFromContext).toBe(true);
expect(entries.map((entry) => entry.type)).toEqual([
"custom_message",
"custom_message",
"message",
"message",
"message",
"message",
]);
expect(entries[0]).toMatchObject({ type: "custom_message", excludeFromContext: true });
expect(harness.sessionManager.buildSessionContext().messages[0]).toMatchObject({
role: "custom",
excludeFromContext: true,
});
expect(harness.session.messages.map((message) => message.role)).toEqual([
"custom",
"custom",
"user",
"assistant",
"toolResult",
"assistant",
]);
});
it("does not emit message_end for bash execution messages", async () => {
@@ -258,96 +258,68 @@ describe("AgentSession queue characterization", () => {
expect(getAssistantTexts(harness)).toEqual(["", "original turn complete", "batched follow-up response"]);
});
it("records excluded custom messages with triggerTurn without starting a provider turn", async () => {
const harness = await createHarness();
harnesses.push(harness);
it("records excluded custom messages immediately without starting provider turns", async () => {
const idleHarness = await createHarness();
harnesses.push(idleHarness);
let providerCalled = false;
harness.setResponses([
idleHarness.setResponses([
() => {
providerCalled = true;
return fauxAssistantMessage("unexpected");
},
]);
await harness.session.sendCustomMessage(
await idleHarness.session.sendCustomMessage(
{ customType: "status", content: "display only", display: true, details: {}, excludeFromContext: true },
{ triggerTurn: true },
);
expect(providerCalled).toBe(false);
expect(harness.session.messages).toHaveLength(1);
expect(harness.session.messages[0]).toMatchObject({
expect(idleHarness.session.messages[0]).toMatchObject({
role: "custom",
customType: "status",
excludeFromContext: true,
});
expect(harness.getPendingResponseCount()).toBe(1);
});
expect(idleHarness.getPendingResponseCount()).toBe(1);
it("records excluded custom messages with deliverAs steer while streaming", async () => {
const waiting = await createWaitingHarness();
const { harness, waitForToolStart, promptPromise, releaseToolExecution } = waiting;
harnesses.push(harness);
let recordedBeforeRelease = false;
for (const deliverAs of ["steer", "followUp"] as const) {
const waiting = await createWaitingHarness();
const { harness, waitForToolStart, promptPromise, releaseToolExecution } = waiting;
harnesses.push(harness);
let providerCalledForQueuedMessage = false;
harness.setResponses([
fauxAssistantMessage(fauxToolCall("wait", {}), { stopReason: "toolUse" }),
fauxAssistantMessage("done"),
() => {
providerCalledForQueuedMessage = true;
return fauxAssistantMessage("unexpected queued turn");
},
]);
harness.setResponses([
fauxAssistantMessage(fauxToolCall("wait", {}), { stopReason: "toolUse" }),
fauxAssistantMessage("done"),
]);
await waitForToolStart;
await harness.session.sendCustomMessage(
{
customType: "status",
content: `${deliverAs} display only`,
display: true,
details: {},
excludeFromContext: true,
},
{ deliverAs },
);
const recordedBeforeRelease = harness.session.messages.some(
(message) => message.role === "custom" && message.customType === "status",
);
releaseToolExecution();
await promptPromise;
await waitForToolStart;
await harness.session.sendCustomMessage(
{ customType: "status", content: "steer display only", display: true, details: {}, excludeFromContext: true },
{ deliverAs: "steer" },
);
recordedBeforeRelease = harness.session.messages.some(
(message) => message.role === "custom" && message.customType === "status",
);
releaseToolExecution();
await promptPromise;
expect(recordedBeforeRelease).toBe(true);
expect(
harness.session.messages.filter((message) => message.role === "custom" && message.customType === "status"),
).toHaveLength(1);
});
it("records excluded custom messages with deliverAs followUp while streaming without starting another turn", async () => {
const waiting = await createWaitingHarness();
const { harness, waitForToolStart, promptPromise, releaseToolExecution } = waiting;
harnesses.push(harness);
let providerCalledForFollowUp = false;
let recordedBeforeRelease = false;
harness.setResponses([
fauxAssistantMessage(fauxToolCall("wait", {}), { stopReason: "toolUse" }),
fauxAssistantMessage("done"),
() => {
providerCalledForFollowUp = true;
return fauxAssistantMessage("unexpected follow-up");
},
]);
await waitForToolStart;
await harness.session.sendCustomMessage(
{
customType: "status",
content: "follow-up display only",
display: true,
details: {},
excludeFromContext: true,
},
{ deliverAs: "followUp" },
);
recordedBeforeRelease = harness.session.messages.some(
(message) => message.role === "custom" && message.customType === "status",
);
releaseToolExecution();
await promptPromise;
expect(recordedBeforeRelease).toBe(true);
expect(providerCalledForFollowUp).toBe(false);
expect(harness.getPendingResponseCount()).toBe(1);
expect(recordedBeforeRelease).toBe(true);
expect(
harness.session.messages.filter((message) => message.role === "custom" && message.customType === "status"),
).toHaveLength(1);
expect(providerCalledForQueuedMessage).toBe(false);
expect(harness.getPendingResponseCount()).toBe(1);
}
});
it("persists excluded custom messages from message_end hooks after the triggering message", async () => {
@@ -31,28 +31,6 @@ describe("AgentSession retry and event characterization", () => {
});
it("retries after a transient error and succeeds", async () => {
const harness = await createHarness({ settings: { retry: { enabled: true, maxRetries: 3, baseDelayMs: 1 } } });
harnesses.push(harness);
const retryEvents: string[] = [];
harness.session.subscribe((event) => {
if (event.type === "auto_retry_start") retryEvents.push(`start:${event.attempt}`);
if (event.type === "auto_retry_end") retryEvents.push(`end:${event.success}`);
});
harness.setResponses([
fauxAssistantMessage("", { stopReason: "error", errorMessage: "overloaded_error" }),
fauxAssistantMessage("recovered"),
]);
await harness.session.prompt("test");
expect(retryEvents).toEqual(["start:1", "end:true"]);
expect(harness.eventsOfType("agent_end").map((event) => event.willRetry)).toEqual([true, false]);
expect(harness.faux.state.callCount).toBe(2);
expect(harness.session.isRetrying).toBe(false);
});
it("retries when an excluded custom message follows the transient error", async () => {
const harness = await createHarness({
settings: { retry: { enabled: true, maxRetries: 3, baseDelayMs: 1 } },
extensionFactories: [
@@ -73,6 +51,12 @@ describe("AgentSession retry and event characterization", () => {
],
});
harnesses.push(harness);
const retryEvents: string[] = [];
harness.session.subscribe((event) => {
if (event.type === "auto_retry_start") retryEvents.push(`start:${event.attempt}`);
if (event.type === "auto_retry_end") retryEvents.push(`end:${event.success}`);
});
harness.setResponses([
fauxAssistantMessage("", { stopReason: "error", errorMessage: "overloaded_error" }),
fauxAssistantMessage("recovered"),
@@ -80,7 +64,10 @@ describe("AgentSession retry and event characterization", () => {
await harness.session.prompt("test");
expect(retryEvents).toEqual(["start:1", "end:true"]);
expect(harness.eventsOfType("agent_end").map((event) => event.willRetry)).toEqual([true, false]);
expect(harness.faux.state.callCount).toBe(2);
expect(harness.session.isRetrying).toBe(false);
expect(
harness.session.messages.some((message) => message.role === "custom" && message.customType === "status"),
).toBe(true);