Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions src/adapters/cursor/protobuf-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ export const CURSOR_ROUTING_LEVEL_PARAMETER_ID = "optimization";
export const CURSOR_EXTERNAL_ROOT_BLOB_LIMIT = 192;
/** Approximate prompt-size guard; tool schemas and protocol framing consume context separately. */
export const CURSOR_EXTERNAL_ROOT_BYTE_LIMIT = 512 * 1024;
/** Bound synchronous replay construction before the smaller wire-size limits are applied. */
export const CURSOR_EXTERNAL_REPLAY_MESSAGE_LIMIT = 4096;
/** Honest placeholder when native Composer history has a toolCall with no matching toolResult. */
export const CURSOR_MISSING_TOOL_RESULT = "[missing tool_result for this tool_use in history]";
/**
Expand Down Expand Up @@ -311,6 +313,7 @@ function rootPromptMessages(
const replayRuns = new Map<RootBlobCandidate["role"], {
text: string;
entry: RootBlobCandidate;
entryIndex: number;
length: number;
}>();
const toolCallCounts = new Map<string, number>();
Expand All @@ -337,17 +340,20 @@ function rootPromptMessages(
// half, so losing it re-primes the self-reinforcing loop the breaker exists to end.
{ ...opts, text: marked, messageIndex: previous.entry.messageIndex ?? opts.messageIndex },
);
entries[entries.indexOf(previous.entry)] = replacement;
replayRuns.set(role, { text: normalized, entry: replacement, length: runLength });
entries[previous.entryIndex] = replacement;
replayRuns.set(role, { text: normalized, entry: replacement, entryIndex: previous.entryIndex, length: runLength });
return;
}
const entry = rootBlobCandidate(payload, role, opts);
entries.push(entry);
replayRuns.set(role, { text: normalized, entry, length: 1 });
replayRuns.set(role, { text: normalized, entry, entryIndex: entries.length - 1, length: 1 });
};

for (let i = 0; i < messages.length; i++) {
if (i === activeUserIndex) break;
const replayEnd = activeUserIndex < 0 ? messages.length : activeUserIndex;
const replayStart = externalModel
? Math.max(0, replayEnd - CURSOR_EXTERNAL_REPLAY_MESSAGE_LIMIT)
: 0;
for (let i = replayStart; i < replayEnd; i++) {
const message = messages[i];
if (!message) continue;
if (message.role === "user" || message.role === "developer") {
Expand Down
32 changes: 31 additions & 1 deletion tests/providers/cursor/cursor-repetition-breaker.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { describe, expect, test } from "bun:test";
import { fromBinary } from "@bufbuild/protobuf";
import { encodeCursorRunRequest } from "../../../src/adapters/cursor/protobuf-request";
import {
CURSOR_EXTERNAL_REPLAY_MESSAGE_LIMIT,
encodeCursorRunRequest,
} from "../../../src/adapters/cursor/protobuf-request";
import { handleCursorNativeKv } from "../../../src/adapters/cursor/native-exec";
import { create } from "@bufbuild/protobuf";
import {
Expand Down Expand Up @@ -169,4 +172,31 @@ describe("cursor external-replay repetition breaker (devlog 260826 gap-9)", () =
expect(texts.filter(text => text.startsWith("[Tool Result]"))).toHaveLength(3);
expect(texts.filter(text => text.includes("same tool call repeated 3 times"))).toHaveLength(1);
});

test("bounds replay construction before processing an oversized history", () => {
const messages: OcxMessage[] = [
{ role: "user", content: "old turn", timestamp: 1 },
...Array.from({ length: 5 }, (_, index) => ({
role: "assistant" as const,
content: REPEAT,
timestamp: index + 2,
})),
...Array.from({ length: CURSOR_EXTERNAL_REPLAY_MESSAGE_LIMIT }, (_, index) => ({
role: "assistant" as const,
content: index % 2 === 0 ? "recent A" : "recent B",
timestamp: index + 7,
})),
{ role: "user", content: "continue", timestamp: CURSOR_EXTERNAL_REPLAY_MESSAGE_LIMIT + 7 },
] as OcxMessage[];

const texts = rootTexts(encode(messages));
expect(texts.some(text => text.includes("Take a DIFFERENT action now"))).toBe(false);
// The oversized history does not ship at all: the envelope sheds every replayed
// entry rather than emitting a weakened note or a partial fragment, so only the
// system prompt root survives. The old REPEAT is provably gone — not just unnoted —
// and no recent tail leaks past the bound either.
expect(texts).toHaveLength(1);
expect(texts.some(text => text === REPEAT)).toBe(false);
expect(texts.some(text => text === "recent A" || text === "recent B")).toBe(false);
});
});
Loading