diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 6750f385d..636ecaf24 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -36,6 +36,7 @@ - Fixed the `pi_read` range translation padding the slice it asks for. `piReadPath` composed a plain `:N+K` selector, which the local `read` tool expands by one leading and three trailing context line — so a frame naming offset 5/limit 20 received lines 4-27. Ranged Pi reads now compose `:raw:N+K`; the wire result is an opaque output string, so the line-number gutter `raw` also drops carries nothing the contract needs. - Fixed ranged legacy Cursor reads reporting the returned window byte length as the full file size. +- Updated the Cursor client build advertisement to activate the modern exec-frame protocol handled by this provider. - Fixed four Cursor exec frames answering with a result whose oneof was never set. In proto3 that is not an empty result — the server reads it as "the tool ran and produced nothing", indistinguishable from real success. `listMcpResourcesExecResult`, `readMcpResourceExecResult`, `recordScreenResult` and `computerUseResult` now send `ListMcpResourcesSuccess{resources: []}`, `ReadMcpResourceNotFound{uri}`, `RecordScreenFailure` and `ComputerUseError` respectively. - The MCP resource frames now answer from the host instead of a fixed verdict. `CursorExecHandlers` gained `listMcpResources`/`readMcpResource`, so a host holding live MCP connections advertises them; the empty catalog and `not_found` above remain the answer when no handler is supplied. A handler that throws surfaces as `ListMcpResourcesError`/`ReadMcpResourceError` rather than collapsing into "none exist", which the model cannot retry. A read carrying `download_path` forwards it and answers with `ReadMcpResourceSuccess.download_path` and no content, which is what that mode means. - Fixed Cursor `connect_scm` calls losing their repository and settling on a fabricated verdict. The target rides in the `ConnectScmArgs.target` oneof, so reading a flat `github` property always saw `undefined`; and the authoritative `success`/`error`/`rejected` result only arrives on the completion frame, so answering at the announcement persisted a fixed failure for every call — including the ones the server went on to accept. The block now opens on the start frame and settles from the completion's decoded result. diff --git a/packages/ai/src/providers/cursor.ts b/packages/ai/src/providers/cursor.ts index 4160141f8..754c892d5 100644 --- a/packages/ai/src/providers/cursor.ts +++ b/packages/ai/src/providers/cursor.ts @@ -212,7 +212,7 @@ import { } from "./cursor/exec-modern"; export const CURSOR_API_URL = "https://api2.cursor.sh"; -export const CURSOR_CLIENT_VERSION = "cli-2026.01.09-231024f"; +export const CURSOR_CLIENT_VERSION = "cli-2026.07.23-e383d2b"; const CURSOR_PROXY_TUNNEL_TIMEOUT_MS = 30_000; diff --git a/packages/ai/test/cursor-exec-modern.test.ts b/packages/ai/test/cursor-exec-modern.test.ts index 68624164c..93cb5a730 100644 --- a/packages/ai/test/cursor-exec-modern.test.ts +++ b/packages/ai/test/cursor-exec-modern.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from "bun:test"; import { create, fromBinary, toBinary } from "@bufbuild/protobuf"; import { type BlockState, + CURSOR_CLIENT_VERSION, flushOpenToolCalls, handleServerMessage, processInteractionUpdate, @@ -192,6 +193,12 @@ function soleResult(frames: AgentClientMessage[]) { return frame.value.message; } +describe("Cursor modern exec protocol activation", () => { + it("advertises the client build whose schema includes modern exec frames", () => { + expect(CURSOR_CLIENT_VERSION).toBe("cli-2026.07.23-e383d2b"); + }); +}); + describe("Cursor stream teardown", () => { it("keeps whole arguments on a block still open when the transport ends", async () => { // A connect-SCM block arrives with complete `arguments` and never feeds