From 6a3675e2257fe6cde2ceba89a7001d3993efdc7f Mon Sep 17 00:00:00 2001 From: luvs01 Date: Fri, 28 Aug 2026 13:22:09 +0900 Subject: [PATCH] fix(cursor): guard suspended checkpoint lookup --- src/adapters/cursor.ts | 1 + src/adapters/cursor/checkpoint-store.ts | 3 +++ src/adapters/cursor/request-builder.ts | 5 ++++- tests/cursor-tool-suspended-checkpoint.test.ts | 18 +++++++++++++++++- 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/adapters/cursor.ts b/src/adapters/cursor.ts index 6761a3d194..0d24281609 100644 --- a/src/adapters/cursor.ts +++ b/src/adapters/cursor.ts @@ -195,6 +195,7 @@ export function createCursorAdapter(provider: OcxProviderConfig, deps: CursorAda coveredMessageCount, prefixDigest: cursorCoveredPrefixDigest(_parsed, coveredMessageCount), systemDigest: cursorInstructionDigest(_parsed), + toolSuspended: toolSuspendedCommit, }); if (!checkpointRef) return; if (previousRef && previousRef !== checkpointRef) invalidateCursorCheckpoint(previousRef); diff --git a/src/adapters/cursor/checkpoint-store.ts b/src/adapters/cursor/checkpoint-store.ts index b9d78d9716..7cd98c5820 100644 --- a/src/adapters/cursor/checkpoint-store.ts +++ b/src/adapters/cursor/checkpoint-store.ts @@ -37,6 +37,7 @@ export interface CursorCheckpointSnapshot { coveredMessageCount?: number; prefixDigest?: string; systemDigest?: string; + toolSuspended?: boolean; } interface CursorCheckpointStore { @@ -187,6 +188,7 @@ export function commitCursorCheckpoint(input: { coveredMessageCount?: number; prefixDigest?: string; systemDigest?: string; + toolSuspended?: boolean; }): string | undefined { if (!input.conversationId || !input.modelId || input.checkpointBytes.byteLength === 0) return undefined; if (input.checkpointBytes.byteLength > CURSOR_CHECKPOINT_MAX_TOTAL_BYTES) return undefined; @@ -216,6 +218,7 @@ export function commitCursorCheckpoint(input: { ...(input.coveredMessageCount !== undefined ? { coveredMessageCount: input.coveredMessageCount } : {}), ...(input.prefixDigest ? { prefixDigest: input.prefixDigest } : {}), ...(input.systemDigest ? { systemDigest: input.systemDigest } : {}), + ...(input.toolSuspended ? { toolSuspended: true } : {}), }; const blobIds = collectCheckpointBlobIds(input.checkpointBytes); if (blobIds === undefined) return undefined; diff --git a/src/adapters/cursor/request-builder.ts b/src/adapters/cursor/request-builder.ts index e99791134a..91aaeb1617 100644 --- a/src/adapters/cursor/request-builder.ts +++ b/src/adapters/cursor/request-builder.ts @@ -431,7 +431,10 @@ function resolveCursorCheckpoint( if (cursorCheckpointModelAffinityId(snapshot.modelId) !== cursorCheckpointModelAffinityId(request.modelId)) { return { reason: "model_changed" }; } - if (parsed.context.messages.at(-1)?.role !== "toolResult" && cursorState?.checkpointUsable === false) { + if ( + parsed.context.messages.at(-1)?.role !== "toolResult" + && (snapshot.toolSuspended === true || cursorState?.checkpointUsable === false) + ) { return { reason: "trailing_tool_result" }; } const lineage = lineageMismatch(parsed, snapshot); diff --git a/tests/cursor-tool-suspended-checkpoint.test.ts b/tests/cursor-tool-suspended-checkpoint.test.ts index 6461d20a48..7b4147f1ae 100644 --- a/tests/cursor-tool-suspended-checkpoint.test.ts +++ b/tests/cursor-tool-suspended-checkpoint.test.ts @@ -1,6 +1,7 @@ import { describe, expect, test } from "bun:test"; import { createCursorAdapter as createCursorAdapterProduction } from "../src/adapters/cursor"; import { clearCursorCheckpointsForTests, getCursorCheckpoint } from "../src/adapters/cursor/checkpoint-store"; +import { createCursorRequest } from "../src/adapters/cursor/request-builder"; import { create, toBinary } from "@bufbuild/protobuf"; import { ConversationStateStructureSchema } from "../src/adapters/cursor/gen/agent_pb"; import type { AdapterEvent, OcxParsedRequest, OcxProviderConfig } from "../src/types"; @@ -55,7 +56,22 @@ describe("tool-suspended checkpoint commit (devlog 260826 050)", () => { if (done?.type !== "done") throw new Error("expected done"); expect(done.providerState?.cursor?.checkpointRef).toBeDefined(); expect(done.providerState?.cursor?.checkpointUsable).toBe(false); - expect(getCursorCheckpoint(done.providerState?.cursor?.checkpointRef)).toBeDefined(); + expect(getCursorCheckpoint(done.providerState?.cursor?.checkpointRef)?.toolSuspended).toBe(true); + + const retry = createCursorRequest(body("cursor/grok-4.6")); + expect(retry.continuationMode).toBe("full-replay"); + expect(retry.checkpointInvalidationReason).toBe("trailing_tool_result"); + + const continuation = body("cursor/grok-4.6"); + continuation.context.messages.push({ + role: "toolResult", + toolCallId: "call_x", + content: "sunny", + timestamp: 2, + }); + const resumed = createCursorRequest(continuation); + expect(resumed.continuationMode).toBe("checkpoint"); + expect(resumed.checkpointBytes).toEqual(checkpointBytes); clearCursorCheckpointsForTests(); });