diff --git a/src/acp-agent.ts b/src/acp-agent.ts index ed3157b9..e4d615ec 100644 --- a/src/acp-agent.ts +++ b/src/acp-agent.ts @@ -8047,6 +8047,13 @@ export class ClaudeAcpAgent { message.message.model === "" && compaction.hasDeliveredOutput ) { + if (isSyntheticUsageLimitMessage(message.message)) { + lastAssistantWasUsageLimit = true; + lastAssistantFailureTitle = assistantMessageText(message.message); + if (message.error) { + lastAssistantError = message.error; + } + } break; } diff --git a/src/tests/acp-agent.test.ts b/src/tests/acp-agent.test.ts index 22b35c78..f64ca194 100644 --- a/src/tests/acp-agent.test.ts +++ b/src/tests/acp-agent.test.ts @@ -12631,6 +12631,56 @@ describe("stop reason propagation", () => { expect((err as { data: unknown }).data).toEqual({ errorKind: "rate_limit" }); }); + it.each( + [false, true].flatMap((typedFailures) => + [undefined, "authentication_failed" as const].map((staleError) => ({ + typedFailures, + staleError, + })), + ), + )( + "preserves a usage-limit failure after compaction (typed: $typedFailures, stale error: $staleError)", + async ({ typedFailures, staleError }) => { + const agent = createMockAgent(); + if (typedFailures) await initializeClient(agent, airSessionFailureCapabilities); + const staleFrame = createAssistantError(staleError, "stale compaction output"); + staleFrame.message.model = ""; + injectSession(agent, [ + { + type: "system", + subtype: "status", + status: null, + compact_result: "success", + uuid: "compact-completed", + session_id: "test-session", + }, + createUsageLimitAssistantError(), + staleFrame, + createResultMessage({ + subtype: "success", + stop_reason: "end_turn", + is_error: true, + result: "private provider detail", + }), + ]); + + const prompt = agent.prompt({ + sessionId: "test-session", + prompt: [{ type: "text", text: "test" }], + }); + if (typedFailures) { + expect(sessionFailureFromResponse(await prompt)).toMatchObject({ + category: "limit", + actions: [], + title: + "You've hit your individual spend limit ยท run /usage-credits to ask your admin for a higher limit", + }); + } else { + await expect(prompt).rejects.toMatchObject({ data: { errorKind: "rate_limit" } }); + } + }, + ); + it("omits errorKind data when no SDKAssistantMessage.error was observed", async () => { const agent = createMockAgent(); injectSession(agent, [