Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Claim ledger — status per cxc-search discipline:
| Some models show 1M "Max Context" in Cursor table (Fable/Opus/Sonnet 5, Gemini) | verified | same (primary) |
| Ultra = 20x usage ($400 API-agent allowance), NOT an exclusive catalog | verified | cursor.com/pricing + forum staff (primary) |
| Max Mode currently documented for legacy request-based plans | verified | prod.cursor.com/help/ai-features/max-mode (primary) |
| K3 1M specifically unlocked on Ultra | UNVERIFIED — user observation; no primary source; Reddit says the 1M option appeared then disappeared | reddit 2026-08 (lead) |
| K3 1M specifically unlocked on Ultra | user-confirmed 2026-08-26 (operator saw the 1M option live in Cursor on the Ultra plan); public primary source still absent | user observation (authoritative for this deployment) + reddit lead |
| Wire: max mode = RequestedModel.max_mode (field 2) AND ModelDetails.max_mode (field 7); missing either can invalid_argument | lead (2 impl sources) | oh-my-pi #4969, cursor-opencode-provider |
| 1M exposure pattern: synthetic <model>-1m picker variant w/ limit.context=1M, wire sends original id + maxMode | lead | cursor-opencode-provider README |
| In-repo: GetUsableModels ModelDetails.maxMode=true observed on 28 -fast ids (260822); no contextTokenLimit field | verified (own probe) | devlog/_plan/260822_senpi_cursor_transfer/210_maxmode.md |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,30 @@ subagent. Honest scope: diagnostics capture, not a behavior fix.

## Accept criteria

## Implementation notes (wpF, 2026-08-26)

- Item 1 (diagnostics) was already satisfied by the baseline: the
run-request diagnostic logs continuationMode +
checkpointInvalidationReason + rootBlobs/rootBytes
(protobuf-request.ts:934-949). No change needed.
- Item 2 landed as a serve-time digest check in native-exec.ts
getBlobArgs: blobs are content-addressed (SHA-256 id), so a served
payload whose digest mismatches its raw 32-byte id is in-store
corruption — the splice signature. Emits
`blob-integrity-mismatch` debug diagnostic (key prefix + byte length
only; no payload).

## G2 stall capture procedure (next occurrence)

1. Reproduce with the SAME thread in the Codex app; note wall-clock time.
2. Mirror the request via curl (session log has the request id):
`curl -N http://localhost:10100/v1/responses -H 'Content-Type: application/json' --data-binary @req.json | tee stall.sse`
3. Enable debug diagnostics (OCX debug env) and capture the
run-request + checkpoint-continuation lines for the stalling turn.
4. Evidence to file here: last SSE event before silence, whether
response.completed arrived, continuationMode of the turn, and any
blob-integrity-mismatch lines.

- Diagnostic line appears for cursor turns under debug flag (test with
debug seam).
- Integrity check triggers on an injected mutated blob (unit test with
Expand Down
14 changes: 13 additions & 1 deletion src/adapters/cursor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,19 @@ export function createCursorAdapter(provider: OcxProviderConfig, deps: CursorAda
|| activeRequest.contextUsageStoreCheckpoints === false
|| !lastTransport?.captured
|| lastTransport.captured.byteLength === 0
) return;
) {
// Refusal diagnostics (devlog 260826 050/080): name the exact guard so a live
// missing_ref chain can be attributed without instrumented rebuilds.
debugProviderDiagnostic("cursor", "checkpoint-commit-refused", {
replayUnsafe,
emittedClientTool,
capturedAfterClientTool,
externalModel: isCursorExternalWireModel(activeRequest.modelId),
storeCheckpoints: activeRequest.contextUsageStoreCheckpoints !== false,
capturedBytes: lastTransport?.captured?.byteLength ?? 0,
});
return;
}
const previousRef = _parsed._providerContinuation?.cursor?.checkpointRef;
const coveredMessageCount = _parsed.context.messages.length;
const checkpointRef = commitCursorCheckpoint({
Expand Down
14 changes: 14 additions & 0 deletions src/adapters/cursor/live-transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,20 @@ export function finalizeAfterDrain(state: ReturnType<typeof createCursorProtobuf
export function clientToolFinalizeGraceMsForRequest(request: CursorRunRequest, baseGraceMs = CLIENT_TOOL_FINALIZE_GRACE_MS): number {
if (request.rawMessages?.at(-1)?.role === "toolResult") return baseGraceMs;
const text = activePromptText(request);
// Parallel-tool requests with several advertised tools get the expanded window regardless of
// prompt shape: external models (grok) assemble sibling calls serially over multiple frames,
// and the 50ms drain grace ended the turn after 1-2 of them (devlog 260826_cursor_responses_gap,
// live 10-parallel probe: calls=2 then calls=1).
if (request.parallelToolCalls === true && (request.tools?.length ?? 0) > 1) {
const advertised = request.tools?.length ?? 0;
return Math.max(
baseGraceMs,
Math.min(
GENERIC_TOOL_COUNT_MAX_FINALIZE_GRACE_MS,
Math.max(GENERIC_TOOL_COUNT_MIN_FINALIZE_GRACE_MS, advertised * GENERIC_TOOL_COUNT_PER_TOOL_GRACE_MS),
),
);
}
if (!cursorRequestHasShellAlias(request.tools) || !isGenericToolUseCountDemoPrompt(text)) return baseGraceMs;
const requestedCount = requestedCursorToolUseCount(text);
const expandedGraceMs = requestedCount
Expand Down
20 changes: 20 additions & 0 deletions src/adapters/cursor/native-exec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,19 @@ export function storeCursorBlob(data: Uint8Array, requestScope?: CursorBlobReque
return blobId;
}

/**
* Serve-time integrity for content-addressed blobs (devlog 260826_cursor_responses_gap 080):
* a raw 32-byte blob id IS the SHA-256 of its bytes, so served data whose digest mismatches
* the id means in-store corruption — the splice signature behind garbled replayed tool
* results. Ids longer than 32 bytes (digested-key namespace) and server-minted ids are not
* content-addressed and always pass.
*/
export function cursorBlobServeIntegrityOk(blobId: Uint8Array, served: Uint8Array): boolean {
if (blobId.byteLength !== 32) return true;
const digest = createHash("sha256").update(served).digest();
return digest.equals(Buffer.from(blobId));
Comment on lines +413 to +416

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Check replay bytes before deriving their content ID

For locally generated replay blobs, storeCursorBlob derives the ID from the same bytes and setBlob stores a clone, so corruption introduced during root assembly or serialization is incorporated into both the payload and its hash and this check returns true. The new test bypasses the store by manually pairing an old digest with mutated bytes, so it does not demonstrate that the actual getBlobArgs path can detect the reported splice. Add the integrity/round-trip check at the assembly boundary, or introduce a real retained-store fault-injection seam and test the emitted diagnostic through handleCursorNativeKv.

AGENTS.md reference: src/AGENTS.md:L24-L26

Useful? React with 👍 / 👎.

}

/**
* Long-lived pin for blobs referenced by an active Cursor conversation checkpoint.
* Unlike a request scope, this lease is not sealed and is not released by getBlob hydration.
Expand Down Expand Up @@ -622,6 +635,13 @@ export function handleCursorNativeKv(
if (kvMsg.message.case === "getBlobArgs") {
const blobKey = key(kvMsg.message.value.blobId);
const blobData = getBlob(blobKey);
// Splice-class corruption guard (devlog 260826 080): diagnostic only, never blocks serving.
if (blobData && !cursorBlobServeIntegrityOk(kvMsg.message.value.blobId, blobData)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid hashing blobs when debug diagnostics are off

On every successful getBlobArgs carrying a 32-byte ID, this condition synchronously hashes the entire served payload before debugProviderDiagnostic gets a chance to discard the event when provider debugging is disabled. With blobs permitted up to 16 MiB, ordinary non-debug requests now incur substantial event-loop work per hydration, and repeated gets can delay stream processing and heartbeats. Gate the integrity calculation on the debug setting, or cache validation performed at admission, so opt-in instrumentation does not add this cost to every Cursor request.

Useful? React with 👍 / 👎.

debugProviderDiagnostic("cursor", "blob-integrity-mismatch", {
blobKey: blobKey.slice(0, 18),
servedBytes: blobData.byteLength,
});
}
if (blobData && requestScope && blobRequestScopes.get(requestScope)?.kind === "request") {
releaseHydratedBlob(blobKey, requestScope);
}
Expand Down
4 changes: 2 additions & 2 deletions src/adapters/cursor/tool-definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,7 @@ export function buildCursorToolGuidanceSystemNote(
? "Your tool list may display it under a longer `mcp_opencodex-responses_shell_command` / `mcp_opencodex-responses_exec_command` name; those are the SAME tool — call whichever your list shows, and do not comment on the naming difference to the user."
: undefined,
hasBareExec
? `Prefer the Codex shell bridge over Cursor-native Shell/Read. If a Cursor-native file read, directory listing, grep, or shell operation is rejected, continue with the listed catalog tool ${shellBridgeLabel}.`
? `NEVER attempt Cursor-native Shell, Read, Grep, List, or any tool not in the catalog above — they are not executed locally in this environment and every attempt wastes a turn and can stall the session. ${shellBridgeLabel} is the ONLY shell surface; go to it directly on the FIRST attempt, never as a fallback after probing a native tool. Do not narrate switching surfaces ("native is blocked, using the bridge instead") — there is exactly one surface.`
: undefined,
hostShellNote,
"Cursor product features (Chronicle, screen recording, Notes, Plans, background agents) are available only if this turn's catalog lists a matching tool; do not offer or promise them otherwise.",
Expand All @@ -687,7 +687,7 @@ export function buildCursorToolGuidanceSystemNote(
: undefined,
"Do not count or report a tool call unless a tool result was actually returned.",
hasBareExec
? `If a Cursor-native file read, directory listing, grep, or shell operation is rejected by the runtime, use ${shellBridgeLabel} with an equivalent host-shell-safe command (POSIX: \`cat\`/\`ls\`/\`rg\`; Windows PowerShell: \`Get-Content\`/\`Get-ChildItem\`/\`Select-String\`). For file edits, use ${structuredEditNames.length > 0 ? `the structured edit tools (${quotedNames(structuredEditNames)}) or ` : ""}\`apply_patch\` when available.`
? `For every file read, directory listing, grep, or shell operation use ${shellBridgeLabel} directly with host-shell-safe commands (POSIX: \`cat\`/\`ls\`/\`rg\`; Windows PowerShell: \`Get-Content\`/\`Get-ChildItem\`/\`Select-String\`). For file edits, use ${structuredEditNames.length > 0 ? `the structured edit tools (${quotedNames(structuredEditNames)}) or ` : ""}\`apply_patch\` when available.`
: undefined,
].filter((note): note is string => typeof note === "string");
return notes.join(" ");
Expand Down
25 changes: 25 additions & 0 deletions tests/cursor-blob-integrity.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { describe, expect, test } from "bun:test";
import { createHash } from "node:crypto";
import { cursorBlobServeIntegrityOk, storeCursorBlob } from "../src/adapters/cursor/native-exec";

describe("cursor blob serve-time integrity (devlog 260826 080)", () => {
test("content-addressed blob passes when bytes match the id", () => {
const data = new TextEncoder().encode('{"role":"user","content":"clean"}');
const id = storeCursorBlob(data);
expect(id.byteLength).toBe(32);
expect(cursorBlobServeIntegrityOk(id, data)).toBe(true);
});

test("mutated bytes are detected (splice fault injection)", () => {
const data = new TextEncoder().encode('{"role":"assistant","content":"[tool_result] output"}');
const id = new Uint8Array(createHash("sha256").update(data).digest());
const corrupted = new TextEncoder().encode('{"role":"assistant","content":"[ martool_result] output"}');
expect(cursorBlobServeIntegrityOk(id, corrupted)).toBe(false);
});

test("non-content-addressed ids (not 32 bytes) always pass", () => {
const served = new TextEncoder().encode("anything");
expect(cursorBlobServeIntegrityOk(new Uint8Array(8), served)).toBe(true);
expect(cursorBlobServeIntegrityOk(new Uint8Array(64), served)).toBe(true);
});
});
9 changes: 5 additions & 4 deletions tests/cursor-tool-definitions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,9 @@ describe("Cursor tool definitions", () => {
expect(note).toContain("current tool catalog as ground truth");
expect(note).toContain("This turn does not expose neighboring-agent tool names `Read`, `Grep`, `Glob`, `Bash`, `LS`");
expect(note).toContain("not an external MCP server tool");
expect(note).toContain("Prefer the Codex shell bridge over Cursor-native Shell/Read");
expect(note).toContain("continue with the listed catalog tool `exec_command`");
expect(note).toContain("NEVER attempt Cursor-native Shell, Read, Grep, List");
expect(note).toContain("`exec_command` is the ONLY shell surface");
expect(note).toContain("never as a fallback after probing a native tool");
expect(note).not.toContain("such as `shell_command` / `exec_command`");
expect(note).not.toContain("Never tell the user");
expect(note).not.toContain("silently call");
Expand All @@ -353,8 +354,8 @@ describe("Cursor tool definitions", () => {
expect(note).toContain("`shell_command`");
expect(note).toContain("`shell_command` and `exec_command` are aliases of the same bridge");
expect(note).toContain("mcp_opencodex-responses_shell_command");
expect(note).toContain("Prefer the Codex shell bridge over Cursor-native Shell/Read");
expect(note).toContain("continue with the listed catalog tool `shell_command`");
expect(note).toContain("NEVER attempt Cursor-native Shell, Read, Grep, List");
expect(note).toContain("`shell_command` is the ONLY shell surface");
expect(note).not.toContain("Never tell the user");
expect(note).not.toContain("silently call");
});
Expand Down
Loading