From deacd53f79e1ff4eaea6baa28a4d849809e8d673 Mon Sep 17 00:00:00 2001 From: luvs01 <27862058+luvs01@users.noreply.github.com> Date: Fri, 21 Aug 2026 04:51:53 +0900 Subject: [PATCH] fix(cursor): catch EOF terminal budget overflow --- src/adapters/cursor/live-transport.ts | 2 +- tests/cursor-hardening.test.ts | 54 +++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/adapters/cursor/live-transport.ts b/src/adapters/cursor/live-transport.ts index 5775de025e..59a19475f7 100644 --- a/src/adapters/cursor/live-transport.ts +++ b/src/adapters/cursor/live-transport.ts @@ -1129,7 +1129,7 @@ class LiveCursorTransport implements CursorTransport { } releaseBacklogLease(); settler.settleFinish(); - }, (err) => { + }).catch((err) => { failAndClear(err instanceof Error ? err : new Error(String(err))); }); }; diff --git a/tests/cursor-hardening.test.ts b/tests/cursor-hardening.test.ts index 1de86d9738..6385fae22a 100644 --- a/tests/cursor-hardening.test.ts +++ b/tests/cursor-hardening.test.ts @@ -623,6 +623,60 @@ describe("Cursor live transport unexpected EOF", () => { expect(seenSessionId).toBe("cursor_from_gjc_session"); }); + test("settles as a failure when clean EOF synthesis exceeds the transport budget", async () => { + const textFrame = encodeConnectFrame(toBinary(AgentServerMessageSchema, create(AgentServerMessageSchema, { + message: { + case: "interactionUpdate", + value: create(InteractionUpdateSchema, { + message: { + case: "textDelta", + value: create(TextDeltaUpdateSchema, { text: "x" }), + }, + }), + }, + }))); + const connectEnd = encodeConnectFrame(new TextEncoder().encode("{}"), { + flags: CONNECT_FLAG_END_STREAM, + }); + + await withDiscoveryServer(stream => { + stream.respond({ ":status": 200, "content-type": "application/connect+proto" }); + stream.end(Buffer.from(new Uint8Array([ + ...Array.from({ length: 37 }, () => [...textFrame]).flat(), + ...connectEnd, + ]))); + }, async baseUrl => { + const budget = createTestTranslatorBudget({ maxTurnBytes: 1_000 }); + const transport = createLiveCursorTransport({ + provider: { adapter: "cursor", baseUrl, apiKey: "test-token" }, + translatorBudget: budget, + firstFrameTimeoutMs: 2_000, + }); + const iterator = transport.run({ + modelId: "composer-2", + conversationId: "cursor_clean_eof_budget_test", + system: [], + messages: [{ role: "user", content: "hello" }], + })[Symbol.asyncIterator](); + let failure: Error | undefined; + try { + expect(await iterator.next()).toMatchObject({ value: { type: "text" } }); + await Bun.sleep(20); + while (!(await iterator.next()).done) {} + } catch (err) { + failure = err instanceof Error ? err : new Error(String(err)); + } finally { + await transport.close?.(); + } + + expect(failure).toMatchObject({ + name: "TranslatorBudgetExceededError", + code: "translation_buffer_limit", + }); + expect(budget.snapshot().currentBytes).toBe(0); + }); + }); + test("synthesizes done after createPlanRequestQuery text on clean Connect EOF", async () => { const planFrame = encodeConnectFrame(toBinary(AgentServerMessageSchema, create(AgentServerMessageSchema, { message: {