diff --git a/apps/mobile/package.json b/apps/mobile/package.json
index 02ba1b20ae859c7a7dd833f2241fc67cd3a1e78c..2b73304bebd16a7eed4001419783cc891f565711 100644
--- a/apps/mobile/package.json
+++ b/apps/mobile/package.json
@@ -70,6 +70,7 @@
"@t3tools/mobile-review-diff-native": "file:./modules/t3-review-diff",
"@t3tools/mobile-terminal-native": "file:./modules/t3-terminal",
"@t3tools/provider-core": "workspace:*",
+ "@t3tools/provider-cursor": "workspace:*",
"@t3tools/provider-muse": "workspace:*",
"@t3tools/provider-opencode": "workspace:*",
"@t3tools/provider-pi": "workspace:*",
diff --git a/apps/mobile/src/components/ProviderIcon.tsx b/apps/mobile/src/components/ProviderIcon.tsx
index dda6e6f3b544c2bbe559a2c3c1f5a906b36a2a7b..f2eb4e3c7f70fd2da7b5f4078278536ba8ba33b5 100644
--- a/apps/mobile/src/components/ProviderIcon.tsx
+++ b/apps/mobile/src/components/ProviderIcon.tsx
@@ -109,17 +109,6 @@ export function ProviderIcon(props: ProviderIconProps) {
);
}
- if (props.provider === "cursor") {
- return (
-
- );
- }
-
const packageIcon = getProviderClient(props.provider)?.icon;
if (packageIcon) {
const themeFill = (fill: { readonly light: string; readonly dark: string }) =>
diff --git a/apps/mobile/src/lib/providerClients.ts b/apps/mobile/src/lib/providerClients.ts
index 11f7e13a3a78dcf6f44ea15cf350deee24b4745a..cb469a28706c86a7c93020238c84ea2d348dc80e 100644
--- a/apps/mobile/src/lib/providerClients.ts
+++ b/apps/mobile/src/lib/providerClients.ts
@@ -1,11 +1,17 @@
import { isProviderDriverKind } from "@t3tools/contracts";
import { makeProviderClientRegistry } from "@t3tools/provider-core/client";
+import { cursorClient } from "@t3tools/provider-cursor/client";
import { museClient } from "@t3tools/provider-muse/client";
import { openCodeClient } from "@t3tools/provider-opencode/client";
import { piClient } from "@t3tools/provider-pi/client";
/** The provider client definitions this mobile build ships. */
-const providerClients = makeProviderClientRegistry([museClient, openCodeClient, piClient]);
+const providerClients = makeProviderClientRegistry([
+ cursorClient,
+ museClient,
+ openCodeClient,
+ piClient,
+]);
/** The client definition for a driver kind, or `undefined` for drivers drawn by hand. */
export function getProviderClient(driver: string | null | undefined) {
diff --git a/apps/server/package.json b/apps/server/package.json
index 11f202120f85a6d19160255608e90e8c08d7b334..de20a01de3b9ce3f30514cded1e4902c132fdd0a 100644
--- a/apps/server/package.json
+++ b/apps/server/package.json
@@ -39,6 +39,7 @@
"@noble/hashes": "catalog:",
"@opencode-ai/sdk": "^1.3.15",
"@t3tools/provider-core": "workspace:*",
+ "@t3tools/provider-cursor": "workspace:*",
"@t3tools/provider-muse": "workspace:*",
"@t3tools/provider-opencode": "workspace:*",
"@t3tools/provider-pi": "workspace:*",
diff --git a/apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.test.ts
deleted file mode 100644
index cf37340d0e181ae3dc9fcd3f839ba6eeccaaac74..0000000000000000000000000000000000000000
--- a/apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.test.ts
+++ /dev/null
@@ -1,956 +0,0 @@
-import * as NodeServices from "@effect/platform-node/NodeServices";
-import { assert, describe, it } from "@effect/vitest";
-import type { InteractionUpdate } from "@cursor/sdk";
-import {
- CursorSettings,
- EnvironmentId,
- MessageId,
- NodeId,
- ProjectId,
- ProviderInstanceId,
- ProviderSessionId,
- RunAttemptId,
- RunId,
- ThreadId,
-} from "@t3tools/contracts";
-import * as DateTime from "effect/DateTime";
-import * as Effect from "effect/Effect";
-import * as FileSystem from "effect/FileSystem";
-import * as Layer from "effect/Layer";
-import * as Option from "effect/Option";
-import * as Path from "effect/Path";
-import * as Schema from "effect/Schema";
-import * as Stream from "effect/Stream";
-
-import * as ServerConfig from "../../config.ts";
-import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";
-import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
-import { ProviderAdapterV2RuntimePolicy } from "@t3tools/provider-core/server/ProviderAdapter";
-import {
- cursorMcpServers,
- cursorRuntimeAgentPolicy,
- cursorSdkModelSelection,
- makeCursorAgentOptions,
- makeCursorAdapterV2,
- nestedToolCallFromEnvelope,
-} from "./CursorAdapterV2.ts";
-import { isCursorCancellationError, loggedCursorAgentOptions } from "./CursorAgentSdk.ts";
-
-const decodeCursorSettings = Schema.decodeEffect(CursorSettings);
-
-describe("CursorAdapterV2", () => {
- it.effect.each([
- { status: "finished", model: undefined, lateModel: undefined },
- { status: "cancelled", model: "claude-opus-4-6", lateModel: undefined },
- { status: "error", model: "custom-fable", lateModel: undefined },
- { status: "finished", model: undefined, lateModel: "gpt-6-sol" },
- { status: "finished", model: "gpt-6-sol", lateModel: null },
- ] as const)(
- "projects Cursor tasks: $status, late model $lateModel",
- ({ status, model, lateModel }) =>
- Effect.gen(function* () {
- const fileSystem = yield* FileSystem.FileSystem;
- const path = yield* Path.Path;
- const workspace = yield* fileSystem.makeTempDirectoryScoped({
- prefix: "cursor-v2-lifecycle-",
- });
- const instanceId = ProviderInstanceId.make("cursor");
- const threadId = ThreadId.make("cursor-lifecycle-thread");
- const modelSelection = { instanceId, model: "composer-2.5" };
- const runtimePolicy = ProviderAdapterV2RuntimePolicy.make({
- runtimeMode: "full-access",
- interactionMode: "default",
- cwd: workspace,
- });
- const adapter = makeCursorAdapterV2({
- instanceId,
- settings: yield* decodeCursorSettings({}),
- environment: { HOME: workspace },
- fileSystem,
- path,
- idAllocator: yield* IdAllocator.IdAllocatorV2,
- serverConfig: yield* ServerConfig.ServerConfig.pipe(
- Effect.provide(
- ServerConfig.layerTest(workspace, { prefix: "cursor-v2-lifecycle-config-" }),
- ),
- ),
- runner: {
- assertComplete: Effect.void,
- open: () =>
- Effect.succeed({
- agentId: "native-cursor-lifecycle",
- listMessages: Effect.succeed([]),
- close: Effect.void,
- send: (input) =>
- Effect.gen(function* () {
- // Recorded Cursor task calls (see fixtures/subagent) stream a
- // partial-tool-call before tool-call-started with the same args.
- // The run then ends without tool-call-completed, which a live
- // run cannot produce on demand.
- const taskToolCall = {
- type: "task" as const,
- args: {
- description: "Review",
- prompt: "Review the code.",
- subagentType: { kind: "unspecified" },
- ...(model === undefined ? {} : { model }),
- agentId: "cursor-task-agent",
- mode: "unspecified" as const,
- },
- };
- const updates =
- lateModel !== undefined
- ? (["tool-call-started", "tool-call-completed"] as const)
- : (["partial-tool-call", "tool-call-started"] as const);
- for (const type of updates) {
- yield* input.onDelta!({
- type,
- modelCallId: "model-call",
- callId: "task-call",
- toolCall: {
- ...taskToolCall,
- args: {
- ...taskToolCall.args,
- ...(type === "tool-call-completed"
- ? { model: lateModel ?? undefined }
- : {}),
- },
- },
- }).pipe(Effect.orDie);
- }
- return {
- agentId: "native-cursor-lifecycle",
- runId: "native-cursor-run",
- wait: Effect.succeed({
- id: "native-cursor-run",
- requestId: "native-request",
- status,
- model: { id: "composer-2.5" },
- durationMs: 1,
- }),
- cancel: Effect.void,
- };
- }),
- }),
- },
- });
- const runtime = yield* adapter.openSession({
- threadId,
- providerSessionId: ProviderSessionId.make("cursor-lifecycle-session"),
- modelSelection,
- runtimePolicy,
- });
- const providerThread = yield* runtime.ensureThread({
- threadId,
- modelSelection,
- runtimePolicy,
- });
- const now = yield* DateTime.now;
- yield* runtime.startTurn({
- threadId,
- providerThread,
- modelSelection,
- runtimePolicy,
- runId: RunId.make("cursor-lifecycle-run"),
- runOrdinal: 1,
- providerTurnOrdinal: 1,
- attemptId: RunAttemptId.make("cursor-lifecycle-attempt"),
- rootNodeId: NodeId.make("cursor-lifecycle-root"),
- appThread: {
- id: threadId,
- projectId: ProjectId.make("cursor-lifecycle-project"),
- createdBy: "user",
- creationSource: "web",
- title: "Cursor lifecycle",
- providerInstanceId: instanceId,
- modelSelection,
- runtimeMode: "full-access",
- interactionMode: "default",
- branch: null,
- worktreePath: null,
- activeProviderThreadId: providerThread.id,
- lineage: { parentThreadId: null, relationshipToParent: null, rootThreadId: threadId },
- forkedFrom: null,
- createdAt: now,
- updatedAt: now,
- archivedAt: null,
- settledOverride: null,
- settledAt: null,
- lastVisitedAt: null,
- deletedAt: null,
- },
- message: {
- messageId: MessageId.make("cursor-lifecycle-message"),
- createdBy: "user",
- creationSource: "web",
- text: "Review the code.",
- attachments: [],
- },
- });
- const events = yield* runtime.events.pipe(
- Stream.takeUntil((event) => event.type === "turn.terminal"),
- Stream.runCollect,
- );
- const rows = events.filter((event) => event.type === "subagent.updated");
- assert.equal(rows[0]?.subagent.status, "running");
- assert.equal(rows[0]?.subagent.model, model ?? null);
- assert.equal(rows.at(-1)?.subagent.model, lateModel ?? model ?? null);
- assert.equal(
- rows.at(-1)?.subagent.status,
- lateModel !== undefined
- ? "completed"
- : status === "finished"
- ? "idle"
- : status === "cancelled"
- ? "cancelled"
- : "failed",
- );
- assert.isNotNull(rows.at(-1)?.subagent.completedAt);
- }).pipe(Effect.scoped, Effect.provide(Layer.merge(NodeServices.layer, IdAllocator.layer))),
- );
-
- it.effect("fails standalone SDK transport diagnostics and sends compaction as /compress", () =>
- Effect.gen(function* () {
- const fileSystem = yield* FileSystem.FileSystem;
- const path = yield* Path.Path;
- const workspace = yield* fileSystem.makeTempDirectoryScoped({ prefix: "cursor-v2-errors-" });
- const sentMessages: Array = [];
- let sendCalls = 0;
- const instanceId = ProviderInstanceId.make("cursor");
- const threadId = ThreadId.make("cursor-transport-error-thread");
- const modelSelection = { instanceId, model: "composer-2.5" };
- const runtimePolicy = ProviderAdapterV2RuntimePolicy.make({
- runtimeMode: "full-access",
- interactionMode: "default",
- cwd: workspace,
- });
- const adapter = makeCursorAdapterV2({
- instanceId,
- settings: yield* decodeCursorSettings({}),
- environment: { HOME: workspace },
- fileSystem,
- path,
- idAllocator: yield* IdAllocator.IdAllocatorV2,
- serverConfig: yield* ServerConfig.ServerConfig.pipe(
- Effect.provide(ServerConfig.layerTest(workspace, { prefix: "cursor-v2-error-config-" })),
- ),
- runner: {
- assertComplete: Effect.void,
- open: () =>
- Effect.succeed({
- agentId: "native-cursor-error",
- listMessages: Effect.succeed([]),
- close: Effect.void,
- send: (input) =>
- Effect.sync(() => {
- sendCalls += 1;
- sentMessages.push(
- typeof input.message === "string" ? input.message : input.message.text,
- );
- const runId = `native-cursor-run-${sendCalls}`;
- return {
- agentId: "native-cursor-error",
- runId,
- wait: Effect.succeed({
- id: runId,
- requestId: `native-request-${sendCalls}`,
- status: "finished" as const,
- model: { id: "composer-2.5" },
- durationMs: 1,
- ...(sendCalls === 1
- ? { result: "Error: RetriableError: WritableIterable is closed" }
- : {}),
- }),
- cancel: Effect.void,
- };
- }),
- }),
- },
- });
- const runtime = yield* adapter.openSession({
- threadId,
- providerSessionId: ProviderSessionId.make("cursor-error-session"),
- modelSelection,
- runtimePolicy,
- });
- const providerThread = yield* runtime.ensureThread({
- threadId,
- modelSelection,
- runtimePolicy,
- });
- const now = yield* DateTime.now;
- const appThread = {
- id: threadId,
- projectId: ProjectId.make("cursor-error-project"),
- createdBy: "user" as const,
- creationSource: "web" as const,
- title: "Cursor transport failure",
- providerInstanceId: instanceId,
- modelSelection,
- runtimeMode: "full-access" as const,
- interactionMode: "default" as const,
- branch: null,
- worktreePath: null,
- activeProviderThreadId: providerThread.id,
- lineage: { parentThreadId: null, relationshipToParent: null, rootThreadId: threadId },
- forkedFrom: null,
- createdAt: now,
- updatedAt: now,
- archivedAt: null,
- settledOverride: null,
- settledAt: null,
- lastVisitedAt: null,
- deletedAt: null,
- };
- yield* runtime.startTurn({
- threadId,
- providerThread,
- modelSelection,
- runtimePolicy,
- runId: RunId.make("cursor-error-run"),
- runOrdinal: 1,
- providerTurnOrdinal: 1,
- attemptId: RunAttemptId.make("cursor-error-attempt"),
- rootNodeId: NodeId.make("cursor-error-root"),
- appThread,
- message: {
- messageId: MessageId.make("cursor-error-message"),
- createdBy: "user",
- creationSource: "web",
- text: "continue",
- attachments: [],
- },
- });
- const first = yield* runtime.events.pipe(
- Stream.filter((event) => event.type === "turn.terminal"),
- Stream.runHead,
- );
- assert.isTrue(Option.isSome(first));
- if (Option.isSome(first)) {
- assert.equal(first.value.status, "failed");
- assert.equal(first.value.failure?.class, "transport_error");
- }
-
- assert.isDefined(runtime.compactThread);
- yield* runtime.compactThread!({
- threadId,
- providerThread,
- modelSelection,
- runtimePolicy,
- runId: RunId.make("cursor-compact-run"),
- runOrdinal: 2,
- providerTurnOrdinal: 2,
- attemptId: RunAttemptId.make("cursor-compact-attempt"),
- rootNodeId: NodeId.make("cursor-compact-root"),
- appThread,
- message: {
- messageId: MessageId.make("cursor-compact-message"),
- createdBy: "user",
- creationSource: "web",
- text: "ignored by compactThread",
- attachments: [],
- },
- });
- yield* runtime.events.pipe(
- Stream.filter((event) => event.type === "turn.terminal"),
- Stream.runHead,
- );
- assert.equal(sentMessages[1], "/compress");
- }).pipe(Effect.scoped, Effect.provide(Layer.merge(NodeServices.layer, IdAllocator.layer))),
- );
-
- it.effect("projects Cursor directory trees and lint diagnostics as file search results", () =>
- Effect.gen(function* () {
- const fileSystem = yield* FileSystem.FileSystem;
- const path = yield* Path.Path;
- const workspace = yield* fileSystem.makeTempDirectoryScoped({ prefix: "cursor-v2-search-" });
- const instanceId = ProviderInstanceId.make("cursor");
- const threadId = ThreadId.make("cursor-search-thread");
- const modelSelection = { instanceId, model: "composer-2.5" };
- const runtimePolicy = ProviderAdapterV2RuntimePolicy.make({
- runtimeMode: "full-access",
- interactionMode: "default",
- cwd: workspace,
- });
- const emptyLsNode = {
- childrenDirs: [],
- childrenFiles: [],
- childrenWereProcessed: true,
- fullSubtreeExtensionCounts: {},
- numFiles: 0,
- };
- const updates: ReadonlyArray = [
- ...(["tool-call-started", "tool-call-completed"] as const).map((type) => ({
- type,
- modelCallId: "native-model-call",
- callId: "mcp-weather",
- toolCall: {
- type: "mcp" as const,
- args: {
- providerIdentifier: "weather",
- toolName: "get_weather",
- args: { city: "Berlin" },
- },
- ...(type === "tool-call-completed"
- ? { result: { status: "success" as const, value: { content: [], isError: false } } }
- : {}),
- },
- })),
- {
- type: "tool-call-completed",
- modelCallId: "native-model-call",
- callId: "read-file",
- toolCall: {
- type: "read",
- args: { path: "src/env.ts" },
- result: {
- status: "success",
- value: { fileSize: 12, content: "---\nfile body", totalLines: 2 },
- },
- },
- },
- {
- type: "tool-call-completed",
- modelCallId: "native-model-call",
- callId: "ls-nested",
- toolCall: {
- type: "ls",
- args: { path: workspace },
- result: {
- status: "success",
- value: {
- directoryTreeRoot: {
- ...emptyLsNode,
- absPath: workspace,
- childrenFiles: [{ name: "README.md" }],
- childrenDirs: [
- {
- ...emptyLsNode,
- absPath: path.join(workspace, "src"),
- childrenFiles: [{ name: "index.ts" }],
- childrenDirs: [
- {
- ...emptyLsNode,
- absPath: path.join(workspace, "src", "nested"),
- childrenFiles: [{ name: "util.ts" }],
- },
- ],
- },
- ],
- },
- },
- },
- },
- },
- {
- type: "tool-call-completed",
- modelCallId: "native-model-call",
- callId: "ls-empty",
- toolCall: {
- type: "ls",
- args: { path: path.join(workspace, "empty") },
- result: {
- status: "success",
- value: {
- directoryTreeRoot: { ...emptyLsNode, absPath: path.join(workspace, "empty") },
- },
- },
- },
- },
- {
- type: "tool-call-completed",
- modelCallId: "native-model-call",
- callId: "ls-failed",
- toolCall: {
- type: "ls",
- args: { path: path.join(workspace, "missing") },
- result: { status: "error", error: "ENOENT" },
- },
- },
- {
- type: "tool-call-completed",
- modelCallId: "native-model-call",
- callId: "grep-failed",
- toolCall: {
- type: "grep",
- args: { pattern: "TODO", path: "src" },
- result: { status: "error", error: "search failed" },
- },
- },
- {
- type: "tool-call-completed",
- modelCallId: "native-model-call",
- callId: "glob-failed",
- toolCall: {
- type: "glob",
- args: { globPattern: "*.ts" },
- result: { status: "error", error: "search failed" },
- },
- },
- {
- type: "tool-call-completed",
- modelCallId: "native-model-call",
- callId: "lints",
- toolCall: {
- type: "readLints",
- args: { paths: ["src/a.ts", "src/b.ts"] },
- result: {
- status: "success",
- value: {
- totalFiles: 2,
- totalDiagnostics: 3,
- fileDiagnostics: [
- {
- path: "src/a.ts",
- diagnosticsCount: 2,
- diagnostics: [
- {
- message: "Unused variable",
- code: "TS6133",
- source: "ts",
- severity: "warning",
- range: { start: { line: 0, character: 4 } },
- },
- {
- message: "Cannot find name",
- code: "TS2304",
- source: "ts",
- severity: "error",
- range: { start: { line: 11 } },
- },
- ],
- },
- {
- path: "src/b.ts",
- diagnosticsCount: 1,
- diagnostics: [
- {
- message: "File-level diagnostic",
- code: "TS0",
- source: "ts",
- severity: "information",
- },
- ],
- },
- ],
- },
- },
- },
- },
- {
- type: "tool-call-completed",
- modelCallId: "native-model-call",
- callId: "lints-empty",
- toolCall: {
- type: "readLints",
- args: { paths: ["src/clean.ts"] },
- result: {
- status: "success",
- value: {
- totalFiles: 1,
- totalDiagnostics: 0,
- fileDiagnostics: [{ path: "src/clean.ts", diagnosticsCount: 0, diagnostics: [] }],
- },
- },
- },
- },
- {
- type: "tool-call-completed",
- modelCallId: "native-model-call",
- callId: "lints-failed",
- toolCall: {
- type: "readLints",
- args: { paths: ["src/a.ts"] },
- result: { status: "error", error: "lint failed" },
- },
- },
- ];
- const adapter = makeCursorAdapterV2({
- instanceId,
- settings: yield* decodeCursorSettings({}),
- environment: { HOME: workspace },
- fileSystem,
- path,
- idAllocator: yield* IdAllocator.IdAllocatorV2,
- serverConfig: yield* ServerConfig.ServerConfig.pipe(
- Effect.provide(ServerConfig.layerTest(workspace, { prefix: "cursor-v2-search-config-" })),
- ),
- runner: {
- assertComplete: Effect.void,
- open: () =>
- Effect.succeed({
- agentId: "native-cursor-search",
- listMessages: Effect.succeed([]),
- close: Effect.void,
- send: (input) =>
- Effect.gen(function* () {
- for (const update of updates) {
- if (input.onDelta !== undefined) {
- yield* input.onDelta(update).pipe(Effect.orDie);
- }
- }
- return {
- agentId: "native-cursor-search",
- runId: "native-cursor-run",
- wait: Effect.succeed({
- id: "native-cursor-run",
- requestId: "native-request",
- status: "finished" as const,
- model: { id: "composer-2.5" },
- durationMs: 1,
- }),
- cancel: Effect.void,
- };
- }),
- }),
- },
- });
- const runtime = yield* adapter.openSession({
- threadId,
- providerSessionId: ProviderSessionId.make("cursor-search-session"),
- modelSelection,
- runtimePolicy,
- });
- const providerThread = yield* runtime.ensureThread({
- threadId,
- modelSelection,
- runtimePolicy,
- });
- const now = yield* DateTime.now;
- yield* runtime.startTurn({
- threadId,
- providerThread,
- modelSelection,
- runtimePolicy,
- runId: RunId.make("cursor-search-run"),
- runOrdinal: 1,
- providerTurnOrdinal: 1,
- attemptId: RunAttemptId.make("cursor-search-attempt"),
- rootNodeId: NodeId.make("cursor-search-root"),
- appThread: {
- id: threadId,
- projectId: ProjectId.make("cursor-search-project"),
- createdBy: "user",
- creationSource: "web",
- title: "Cursor search results",
- providerInstanceId: instanceId,
- modelSelection,
- runtimeMode: "full-access",
- interactionMode: "default",
- branch: null,
- worktreePath: null,
- activeProviderThreadId: providerThread.id,
- lineage: { parentThreadId: null, relationshipToParent: null, rootThreadId: threadId },
- forkedFrom: null,
- createdAt: now,
- updatedAt: now,
- archivedAt: null,
- settledOverride: null,
- settledAt: null,
- lastVisitedAt: null,
- deletedAt: null,
- },
- message: {
- messageId: MessageId.make("cursor-search-message"),
- createdBy: "user",
- creationSource: "web",
- text: "inspect the workspace",
- attachments: [],
- },
- });
- const events = yield* runtime.events.pipe(
- Stream.takeUntil((event) => event.type === "turn.terminal"),
- Stream.runCollect,
- );
- const mcpItems = events.flatMap((event) =>
- event.type === "turn_item.updated" &&
- event.turnItem.type === "dynamic_tool" &&
- event.turnItem.toolName === "mcp__weather__get_weather"
- ? [event.turnItem]
- : [],
- );
- assert.deepEqual(
- mcpItems.map((item) => item.status),
- ["running", "completed"],
- );
- for (const item of mcpItems) {
- assert.equal(item.title, "get weather");
- assert.deepEqual(item.toolSource, {
- key: "mcp:weather",
- name: "weather",
- kind: "integration",
- });
- assert.deepEqual(item.input, {
- providerIdentifier: "weather",
- toolName: "get_weather",
- args: { city: "Berlin" },
- });
- }
- const fileSearchItems = events.flatMap((event) =>
- event.type === "turn_item.updated" &&
- event.turnItem.type === "file_search" &&
- event.turnItem.status !== "running"
- ? [event.turnItem]
- : [],
- );
- const readItems = events.flatMap((event) =>
- event.type === "turn_item.updated" &&
- event.turnItem.type === "dynamic_tool" &&
- event.turnItem.toolName === "Read" &&
- event.turnItem.status === "completed"
- ? [event.turnItem]
- : [],
- );
- assert.deepEqual(
- readItems.map((item) => ({ title: item.title, input: item.input })),
- [{ title: "Read src/env.ts", input: { path: "src/env.ts" } }],
- );
- assert.deepEqual(
- fileSearchItems.map((item) => ({
- pattern: item.pattern,
- status: item.status,
- results: item.results,
- })),
- [
- {
- pattern: workspace,
- status: "completed",
- results: [
- { fileName: path.join(workspace, "README.md") },
- { fileName: path.join(workspace, "src", "index.ts") },
- { fileName: path.join(workspace, "src", "nested", "util.ts") },
- ],
- },
- {
- pattern: path.join(workspace, "empty"),
- status: "completed",
- results: undefined,
- },
- {
- pattern: path.join(workspace, "missing"),
- status: "failed",
- results: [{ fileName: path.join(workspace, "missing"), preview: "ENOENT" }],
- },
- {
- pattern: "TODO",
- status: "failed",
- results: [{ fileName: "src", preview: "search failed" }],
- },
- {
- pattern: "*.ts",
- status: "failed",
- results: [{ fileName: ".", preview: "search failed" }],
- },
- {
- pattern: "src/a.ts, src/b.ts",
- status: "completed",
- results: [
- { fileName: "src/a.ts", line: 1, column: 5, preview: "Unused variable" },
- { fileName: "src/a.ts", line: 12, preview: "Cannot find name" },
- { fileName: "src/b.ts", preview: "File-level diagnostic" },
- ],
- },
- {
- pattern: "src/clean.ts",
- status: "completed",
- results: undefined,
- },
- {
- pattern: "src/a.ts",
- status: "failed",
- results: [{ fileName: "src/a.ts", preview: "lint failed" }],
- },
- ],
- );
- }).pipe(Effect.scoped, Effect.provide(Layer.merge(NodeServices.layer, IdAllocator.layer))),
- );
-
- it("maps Cursor auto and model parameters to SDK selections", () => {
- assert.deepEqual(
- cursorSdkModelSelection({
- instanceId: ProviderInstanceId.make("cursor"),
- model: "auto",
- options: [
- { id: "thinking", value: "high" },
- { id: "contextWindow", value: "1m" },
- { id: "fastMode", value: true },
- ],
- }),
- {
- id: "default",
- params: [
- { id: "thinking", value: "high" },
- { id: "context", value: "1m" },
- { id: "fast", value: "true" },
- ],
- },
- );
- });
-
- it("maps runtime modes to the SDK sandbox and auto-review controls", () => {
- const base = {
- interactionMode: "default" as const,
- cwd: "/tmp/cursor-adapter",
- };
- assert.deepEqual(
- cursorRuntimeAgentPolicy({
- ...base,
- runtimeMode: "full-access",
- }),
- {
- autoReview: false,
- sandboxEnabled: false,
- },
- );
- assert.deepEqual(
- cursorRuntimeAgentPolicy({
- ...base,
- runtimeMode: "auto-accept-edits",
- }),
- {
- autoReview: false,
- sandboxEnabled: true,
- },
- );
- assert.deepEqual(
- cursorRuntimeAgentPolicy({
- ...base,
- runtimeMode: "approval-required",
- }),
- {
- autoReview: true,
- sandboxEnabled: true,
- },
- );
- assert.deepEqual(
- cursorRuntimeAgentPolicy({
- ...base,
- runtimeMode: "full-access",
- approvalPolicy: "never",
- sandboxPolicy: { type: "readOnly" },
- }),
- {
- autoReview: false,
- sandboxEnabled: true,
- },
- );
- assert.deepEqual(
- cursorRuntimeAgentPolicy({
- ...base,
- runtimeMode: "approval-required",
- approvalPolicy: "never",
- sandboxPolicy: { type: "dangerFullAccess" },
- }),
- {
- autoReview: false,
- sandboxEnabled: false,
- },
- );
- });
-
- it("loads the user's Cursor settings layers in every runtime mode", () => {
- // The SDK loads no rules, skills, hooks, or MCP config from disk unless
- // settingSources names them, so an omitted list silently drops AGENTS.md.
- for (const runtimeMode of ["full-access", "auto-accept-edits", "approval-required"] as const) {
- const options = makeCursorAgentOptions({
- modelSelection: {
- instanceId: ProviderInstanceId.make("cursor"),
- model: "composer-2.5",
- },
- runtimePolicy: { runtimeMode, interactionMode: "default", cwd: "/workspace" },
- threadId: ThreadId.make("thread-cursor-setting-sources"),
- });
- assert.deepEqual(options.local?.settingSources, [
- "project",
- "user",
- "team",
- "mdm",
- "plugins",
- ]);
- }
- });
-
- it("injects thread-scoped MCP credentials without logging them", () => {
- const threadId = ThreadId.make("thread-cursor-mcp");
- McpProviderSession.setMcpProviderSession({
- environmentId: EnvironmentId.make("environment-cursor-mcp"),
- threadId,
- providerSessionId: "mcp-session-cursor",
- providerInstanceId: ProviderInstanceId.make("cursor"),
- endpoint: "http://127.0.0.1:43123/mcp",
- authorizationHeader: "Bearer secret-cursor-mcp-token",
- browserToolsAvailable: true,
- });
-
- try {
- assert.deepEqual(cursorMcpServers(threadId), {
- "t3-code": {
- type: "http",
- url: "http://127.0.0.1:43123/mcp",
- headers: {
- Authorization: "Bearer secret-cursor-mcp-token",
- },
- },
- });
-
- const options = makeCursorAgentOptions({
- apiKey: "secret-cursor-api-key",
- modelSelection: {
- instanceId: ProviderInstanceId.make("cursor"),
- model: "composer-2.5",
- },
- runtimePolicy: {
- runtimeMode: "full-access",
- interactionMode: "default",
- cwd: "/workspace",
- },
- threadId,
- });
- assert.deepEqual(options.mcpServers, cursorMcpServers(threadId));
-
- const logged = JSON.stringify(loggedCursorAgentOptions(options));
- assert.notInclude(logged, "secret-cursor-api-key");
- assert.notInclude(logged, "secret-cursor-mcp-token");
- } finally {
- McpProviderSession.clearMcpProviderSession(threadId);
- }
- });
-
- it("recognizes direct and SDK-wrapped abort failures as cancellation", () => {
- assert.isTrue(isCursorCancellationError({ name: "AbortError" }));
- assert.isTrue(
- isCursorCancellationError({
- name: "ConnectError",
- cause: {
- name: "ConnectError",
- cause: { name: "AbortError" },
- },
- }),
- );
- assert.isFalse(isCursorCancellationError(new Error("request failed")));
- assert.isFalse(isCursorCancellationError(null));
- });
-
- it("preserves failed nested read calls when Cursor omits their path", () => {
- assert.deepEqual(
- nestedToolCallFromEnvelope({
- toolCallId: "tool:failed-read",
- readToolCall: {
- args: {},
- result: { error: "File path was not provided." },
- },
- }),
- {
- callId: "tool:failed-read",
- toolCall: {
- type: "read",
- args: { path: "" },
- result: {
- status: "error",
- error: "File path was not provided.",
- },
- },
- },
- );
- });
-});
diff --git a/apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.testkit.ts b/apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.testkit.ts
index a3a72f7411380e5a5d64eac4fe3c0b4af778d706..3f2f6581f90d4af73b1471166b85ef07eef59115 100644
--- a/apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.testkit.ts
+++ b/apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.testkit.ts
@@ -1,6 +1,4 @@
import { HostProcessEnvironment } from "@t3tools/shared/hostProcess";
-import * as OtelEnvironment from "@t3tools/shared/otelEnvironment";
-import { DEFAULT_SIGNAL_EXPORT } from "@t3tools/shared/observability";
import type { InteractionUpdate, RunResult } from "@cursor/sdk";
import * as NodeServices from "@effect/platform-node/NodeServices";
import {
@@ -15,23 +13,21 @@ import * as Effect from "effect/Effect";
import * as Fiber from "effect/Fiber";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
-import * as Path from "effect/Path";
-import * as PlatformError from "effect/PlatformError";
import * as Schema from "effect/Schema";
-import * as ServerConfig from "../../config.ts";
+import { layerTestProviderHost } from "@t3tools/provider-testing/host";
import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
import { ProviderAdapterDriverCreateError } from "@t3tools/provider-core/server/adapterDriver";
import * as ProviderAdapterRegistry from "../ProviderAdapterRegistry.ts";
import type { OrchestratorV2ProviderReplayHarness } from "../testkit/ProviderReplayHarness.ts";
-import * as CursorAgentSdk from "./CursorAgentSdk.ts";
+import { CursorAdapterV2Driver } from "@t3tools/provider-cursor/server";
+import * as CursorAgentSdk from "@t3tools/provider-cursor/server/CursorAgentSdk";
import {
CURSOR_DEFAULT_INSTANCE_ID,
CURSOR_DRIVER_KIND,
- CursorAdapterV2Driver,
cursorSdkModelSelection,
makeCursorAgentOptions,
-} from "./CursorAdapterV2.ts";
+} from "@t3tools/provider-cursor/testing";
import type { ProviderAdapterV2RuntimePolicy } from "@t3tools/provider-core/server/ProviderAdapter";
import type { RuntimePolicyV2Override } from "../RuntimePolicy.ts";
@@ -513,91 +509,6 @@ function layerCursorAgentSdkReplay(
);
}
-function makeReplayServerConfig(
- scenario: string,
-): Effect.Effect<
- ServerConfig.ServerConfig["Service"],
- PlatformError.PlatformError,
- FileSystem.FileSystem | Path.Path
-> {
- return Effect.gen(function* () {
- const fs = yield* FileSystem.FileSystem;
- const path = yield* Path.Path;
- const baseDir = yield* fs.makeTempDirectory({
- prefix: `t3-orchestration-v2-cursor-${scenario}-`,
- });
- const stateDir = path.join(baseDir, "userdata");
- const logsDir = path.join(stateDir, "logs");
- const providerLogsDir = path.join(logsDir, "provider");
- const terminalLogsDir = path.join(logsDir, "terminals");
- const attachmentsDir = path.join(stateDir, "attachments");
- const environmentThemesDir = path.join(stateDir, "themes");
- const worktreesDir = path.join(baseDir, "worktrees");
- const providerStatusCacheDir = path.join(baseDir, "caches");
- for (const directory of [
- stateDir,
- logsDir,
- providerLogsDir,
- terminalLogsDir,
- attachmentsDir,
- environmentThemesDir,
- worktreesDir,
- providerStatusCacheDir,
- ]) {
- yield* fs.makeDirectory(directory, { recursive: true });
- }
- return {
- logLevel: "Error",
- traceMinLevel: "Info",
- traceTimingEnabled: true,
- traceBatchWindowMs: 200,
- traceMaxBytes: 10 * 1024 * 1024,
- traceMaxFiles: 10,
- otelEnvironment: OtelEnvironment.none,
- otlpTracesUrl: undefined,
- otlpMetricsUrl: undefined,
- otlpLogsUrl: undefined,
- otlpTracesExport: DEFAULT_SIGNAL_EXPORT,
- otlpMetricsExport: DEFAULT_SIGNAL_EXPORT,
- otlpLogsExport: DEFAULT_SIGNAL_EXPORT,
- mode: "web",
- port: 0,
- host: undefined,
- cwd: process.cwd(),
- baseDir,
- staticDir: undefined,
- devUrl: undefined,
- devAllowedOrigins: [],
- noBrowser: false,
- startupPresentation: "browser",
- tailscaleServeEnabled: false,
- tailscaleServePort: 443,
- desktopBootstrapToken: undefined,
- autoBootstrapProjectFromCwd: false,
- logWebSocketEvents: false,
- stateDir,
- dbPath: path.join(stateDir, "state.sqlite"),
- keybindingsConfigPath: path.join(stateDir, "keybindings.json"),
- settingsPath: path.join(stateDir, "settings.json"),
- providerStatusCacheDir,
- worktreesDir,
- attachmentsDir,
- browserArtifactsDir: path.join(stateDir, "browser-artifacts"),
- environmentThemesDir,
- logsDir,
- serverLogPath: path.join(logsDir, "server.log"),
- serverTracePath: path.join(logsDir, "server.trace.ndjson"),
- providerLogsDir,
- providerEventLogPath: path.join(providerLogsDir, "events.log"),
- terminalLogsDir,
- anonymousIdPath: path.join(stateDir, "anonymous-id"),
- environmentIdPath: path.join(stateDir, "environment-id"),
- serverRuntimeStatePath: path.join(stateDir, "server-runtime.json"),
- secretsDir: path.join(stateDir, "secrets"),
- };
- });
-}
-
export function layer(
transcript: CursorAgentSdkReplayTranscript,
options?: {
@@ -605,10 +516,6 @@ export function layer(
readonly assertCompleteOnFinalize?: boolean;
},
) {
- const layerServerConfig = Layer.effect(
- ServerConfig.ServerConfig,
- makeReplayServerConfig(transcript.scenario).pipe(Effect.orDie),
- ).pipe(Layer.provide(NodeServices.layer));
// Skill discovery also scans user roots under HOME; an empty HOME keeps
// replays from picking up the host's own skills.
const layerHostEnvironment = Layer.effect(
@@ -630,7 +537,7 @@ export function layer(
Layer.provide(
Layer.mergeAll(
layerCursorAgentSdkReplay(transcript, options),
- layerServerConfig,
+ layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),
layerHostEnvironment,
NodeServices.layer,
IdAllocator.layer,
diff --git a/apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.ts
deleted file mode 100644
index 273564e786c4949dd90b0814471accf62ea601d7..0000000000000000000000000000000000000000
--- a/apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.ts
+++ /dev/null
@@ -1,2685 +0,0 @@
-import type {
- AgentMessage,
- AgentOptions,
- InteractionUpdate,
- McpServerConfig,
- RunResult,
- SDKUserMessage,
- SettingSource,
- ToolCall,
-} from "@cursor/sdk";
-import { formatReadToolLabel, formatSearchToolLabel } from "@t3tools/shared/toolActivity";
-import { HostProcessEnvironment } from "@t3tools/shared/hostProcess";
-import {
- CursorSettings,
- isOrchestrationV2WorkActive,
- defaultInstanceIdForDriver,
- type ChatAttachment,
- type ModelSelection,
- type OrchestrationV2ConversationMessage,
- type OrchestrationV2ExecutionNode,
- type OrchestrationV2PlanArtifact,
- type OrchestrationV2PlanStep,
- type OrchestrationV2ProviderCapabilities,
- type OrchestrationV2ProviderFailure,
- type OrchestrationV2ProviderSession,
- type OrchestrationV2ProviderThread,
- type OrchestrationV2ProviderTurn,
- type OrchestrationV2Subagent,
- type OrchestrationV2TurnItem,
- type ProviderInstanceId,
- type ThreadId,
-} from "@t3tools/contracts";
-import * as DateTime from "effect/DateTime";
-import * as Deferred from "effect/Deferred";
-import * as Effect from "effect/Effect";
-import * as FileSystem from "effect/FileSystem";
-import * as Layer from "effect/Layer";
-import * as Option from "effect/Option";
-import * as Path from "effect/Path";
-import * as Queue from "effect/Queue";
-import * as Ref from "effect/Ref";
-import * as Schema from "effect/Schema";
-import * as Stream from "effect/Stream";
-
-import { resolveAttachmentPath } from "../../attachmentStore.ts";
-import * as ServerConfig from "../../config.ts";
-import { mcpToolPresentation } from "@t3tools/provider-core/server/mcpToolPresentation";
-import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";
-import { CursorTransportFailure } from "../../provider/acp/CursorTransportFailure.ts";
-import { cursorSdkModelSelection } from "../../provider/cursorSdkModel.ts";
-import {
- discoverCursorSkills,
- hasCursorSkillMention,
- rewriteCursorSkillMentions,
-} from "../../provider/Drivers/CursorSkills.ts";
-import { mergeProviderInstanceEnvironment } from "@t3tools/provider-core/server/instanceEnvironment";
-import { t3OrchestrationPromptForFirstRun } from "@t3tools/provider-core/server/orchestrationInstructions";
-import { buildRuntimeInstructions } from "@t3tools/provider-core/server/runtimeInstructions";
-import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
-import { makeProviderFailure } from "@t3tools/provider-core/server/failure";
-import { turnScopedSelectionTransition } from "@t3tools/provider-core/server/selectionTransition";
-import {
- isProviderNativeImageAttachment,
- providerMessageTextWithAttachmentPaths,
-} from "@t3tools/provider-core/server/attachmentPrompt";
-import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";
-import {
- ProviderAdapterDriverCreateError,
- type ProviderAdapterDriver,
- type ProviderAdapterDriverCreateInput,
-} from "@t3tools/provider-core/server/adapterDriver";
-import {
- makeSubagentChildThread,
- makeSubagentConversationArtifacts,
- subagentThreadTitle,
-} from "@t3tools/provider-core/server/subagentProjection";
-import * as CursorAgentSdk from "./CursorAgentSdk.ts";
-export { cursorSdkModelSelection } from "../../provider/cursorSdkModel.ts";
-
-export const CURSOR_DRIVER_KIND = CursorAgentSdk.CURSOR_PROVIDER;
-export const CURSOR_DEFAULT_INSTANCE_ID = defaultInstanceIdForDriver(CURSOR_DRIVER_KIND);
-const DEFAULT_CURSOR_SETTINGS = Schema.decodeSync(CursorSettings)({});
-
-export const CursorProviderCapabilitiesV2 = {
- sessions: {
- supportsMultipleProviderThreadsPerSession: false,
- supportsModelSwitchInSession: true,
- supportsProviderSwitchingViaHandoff: true,
- supportsRuntimeModeSwitchInSession: false,
- pendingRequestsSurviveRestart: false,
- },
- threads: {
- canCreateEmptyThread: true,
- canReadThreadSnapshot: true,
- canRollbackThread: false,
- canForkThread: false,
- canForkFromTurn: false,
- canForkFromSubagentThread: false,
- exposesNativeThreadId: true,
- },
- turns: {
- exposesNativeTurnId: true,
- emitsTurnStarted: true,
- emitsTurnCompleted: true,
- supportsInterrupt: true,
- supportsActiveSteering: false,
- supportsSteeringByInterruptRestart: true,
- supportsQueuedMessages: true,
- terminalStatusQuality: "strong",
- },
- streaming: {
- streamsAssistantText: true,
- streamsReasoning: true,
- streamsToolOutput: true,
- streamsPlanText: true,
- emitsMessageCompleted: true,
- },
- tools: {
- exposesToolItemIds: true,
- emitsToolStarted: true,
- emitsToolCompleted: true,
- emitsToolOutput: true,
- supportsMcpTools: true,
- supportsDynamicToolCallbacks: false,
- },
- approvals: {
- supportsCommandApproval: false,
- supportsFileReadApproval: false,
- supportsFileChangeApproval: false,
- supportsApplyPatchApproval: false,
- approvalsHaveNativeRequestIds: false,
- approvalCallbacksAreLiveOnly: false,
- approvalsCanOriginateFromSubagents: false,
- },
- planning: {
- emitsPlanUpdated: true,
- emitsTodoList: true,
- emitsProposedPlan: true,
- supportsStructuredQuestions: false,
- planDeltasHaveItemIds: true,
- },
- subagents: {
- supportsSubagents: true,
- exposesSubagentThreadIds: false,
- emitsSubagentLifecycle: true,
- canWaitForSubagents: true,
- canCloseSubagents: false,
- canForkSubagentThread: false,
- },
- context: {
- acceptsSystemContext: false,
- acceptsDeveloperContext: false,
- acceptsSyntheticUserContext: true,
- canGenerateSummaries: true,
- canConsumeHandoffSummaries: true,
- supportsDeltaHandoff: true,
- supportsFullThreadHandoff: true,
- maxRecommendedHandoffChars: null,
- },
- checkpointing: {
- appCanCheckpointFilesystem: true,
- supportsNestedCheckpointScopes: true,
- providerCanRollbackConversation: false,
- providerRollbackReturnsSnapshot: false,
- providerCanReadConversationSnapshot: true,
- },
- identity: {
- nativeThreadIds: "strong",
- nativeTurnIds: "strong",
- nativeItemIds: "weak",
- nativeRequestIds: "none",
- },
- runtimePolicy: {
- enforcement: "native",
- },
-} satisfies OrchestrationV2ProviderCapabilities;
-
-export interface CursorRuntimeAgentPolicy {
- readonly autoReview: boolean;
- readonly sandboxEnabled: boolean;
-}
-
-export function cursorRuntimeAgentPolicy(
- runtimePolicy: ProviderAdapter.ProviderAdapterV2RuntimePolicy,
-): CursorRuntimeAgentPolicy {
- const sandboxPolicyType =
- typeof runtimePolicy.sandboxPolicy === "object" &&
- runtimePolicy.sandboxPolicy !== null &&
- "type" in runtimePolicy.sandboxPolicy &&
- typeof runtimePolicy.sandboxPolicy.type === "string"
- ? runtimePolicy.sandboxPolicy.type
- : undefined;
- return {
- autoReview:
- runtimePolicy.approvalPolicy === undefined
- ? runtimePolicy.runtimeMode === "approval-required"
- : runtimePolicy.approvalPolicy !== "never",
- sandboxEnabled:
- runtimePolicy.sandboxPolicy === undefined
- ? runtimePolicy.runtimeMode !== "full-access"
- : sandboxPolicyType !== "dangerFullAccess",
- };
-}
-
-export function cursorMcpServers(threadId: ThreadId): Record | undefined {
- const session = McpProviderSession.readMcpProviderSession(threadId);
- if (session === undefined) {
- return undefined;
- }
- return {
- "t3-code": {
- type: "http",
- url: session.endpoint,
- headers: {
- Authorization: session.authorizationHeader,
- },
- },
- };
-}
-
-function providerSession(input: {
- readonly providerSessionId: OrchestrationV2ProviderSession["id"];
- readonly providerInstanceId: ProviderInstanceId;
- readonly cwd: string | null;
- readonly model: string;
- readonly now: DateTime.Utc;
-}): OrchestrationV2ProviderSession {
- return {
- id: input.providerSessionId,
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- providerInstanceId: input.providerInstanceId,
- status: "ready",
- cwd: input.cwd ?? process.cwd(),
- model: input.model,
- capabilities: CursorProviderCapabilitiesV2,
- createdAt: input.now,
- updatedAt: input.now,
- lastError: null,
- };
-}
-
-function makeProviderThread(input: {
- readonly idAllocator: IdAllocator.IdAllocatorV2Shape;
- readonly providerInstanceId: ProviderInstanceId;
- readonly appThreadId: OrchestrationV2ProviderThread["appThreadId"];
- readonly providerSessionId: OrchestrationV2ProviderThread["providerSessionId"];
- readonly nativeThreadId: string;
- readonly ownerNodeId?: OrchestrationV2ProviderThread["ownerNodeId"];
- readonly forkedFrom?: OrchestrationV2ProviderThread["forkedFrom"];
- readonly now: DateTime.Utc;
-}): OrchestrationV2ProviderThread {
- return {
- id: input.idAllocator.derive.providerThread({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeThreadId: input.nativeThreadId,
- }),
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- providerInstanceId: input.providerInstanceId,
- providerSessionId: input.providerSessionId,
- appThreadId: input.appThreadId,
- ownerNodeId: input.ownerNodeId ?? null,
- nativeThreadRef: {
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeId: input.nativeThreadId,
- strength: "strong",
- },
- nativeConversationHeadRef: null,
- status: "idle",
- firstRunOrdinal: null,
- lastRunOrdinal: null,
- handoffIds: [],
- forkedFrom: input.forkedFrom ?? null,
- createdAt: input.now,
- updatedAt: input.now,
- };
-}
-
-function nativeThreadId(providerThread: OrchestrationV2ProviderThread): string {
- const id = providerThread.nativeThreadRef?.nativeId;
- if (id === null || id === undefined) {
- throw new ProviderAdapter.ProviderAdapterProtocolError({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- detail: `Provider thread ${providerThread.id} is missing its Cursor agent id.`,
- });
- }
- return id;
-}
-
-/**
- * Every Cursor settings layer the Cursor CLI loads: project and user rules,
- * skills, hooks, and MCP servers, team and MDM admin policy, and account
- * plugins. The SDK loads none of them when `settingSources` is omitted.
- * Sandbox policy files are read either way, and hooks can only deny or ask
- * (which local SDK runs reject), so these layers do not loosen the sandbox or
- * approval mode T3 sets.
- */
-const CURSOR_AGENT_SETTING_SOURCES = [
- "project",
- "user",
- "team",
- "mdm",
- "plugins",
-] as const satisfies ReadonlyArray;
-
-export function makeCursorAgentOptions(input: {
- readonly apiKey?: string;
- readonly modelSelection: ModelSelection;
- readonly runtimePolicy: ProviderAdapter.ProviderAdapterV2RuntimePolicy;
- readonly threadId: ThreadId;
-}): AgentOptions {
- const policy = cursorRuntimeAgentPolicy(input.runtimePolicy);
- const mcpServers = cursorMcpServers(input.threadId);
- return {
- model: cursorSdkModelSelection(input.modelSelection),
- name: `T3 Code ${input.threadId}`,
- mode: input.runtimePolicy.interactionMode === "plan" ? "plan" : "agent",
- ...(input.apiKey === undefined ? {} : { apiKey: input.apiKey }),
- local: {
- ...(input.runtimePolicy.cwd === null ? {} : { cwd: input.runtimePolicy.cwd }),
- autoReview: policy.autoReview,
- settingSources: [...CURSOR_AGENT_SETTING_SOURCES],
- sandboxOptions: {
- enabled: policy.sandboxEnabled,
- },
- enableAgentRetries: true,
- },
- ...(mcpServers === undefined ? {} : { mcpServers }),
- };
-}
-
-function stableJson(value: unknown): string {
- try {
- return JSON.stringify(value, null, 2);
- } catch {
- return String(value);
- }
-}
-
-function unknownRecord(value: unknown): Record | undefined {
- return typeof value === "object" && value !== null && !Array.isArray(value)
- ? (value as Record)
- : undefined;
-}
-
-function nestedString(value: unknown, keys: ReadonlyArray): string | undefined {
- let current: unknown = value;
- for (const key of keys) {
- current = unknownRecord(current)?.[key];
- }
- return typeof current === "string" ? current : undefined;
-}
-
-function cursorToolFailed(toolCall: ToolCall): boolean {
- if (toolCall.result?.status === "error") {
- return true;
- }
- return toolCall.type === "mcp" && toolCall.result?.status === "success"
- ? toolCall.result.value.isError
- : false;
-}
-
-function cursorToolOutput(toolCall: ToolCall): unknown {
- const result = toolCall.result;
- if (result === undefined) {
- return undefined;
- }
- return result.status === "success" ? result.value : result.error;
-}
-
-function cursorToolOutputText(toolCall: ToolCall): string {
- if (toolCall.type === "shell" && toolCall.result?.status === "success") {
- return [toolCall.result.value.stdout, toolCall.result.value.stderr]
- .filter((part) => part.length > 0)
- .join("\n");
- }
- if (toolCall.type === "mcp" && toolCall.result?.status === "success") {
- return toolCall.result.value.content
- .flatMap((part) => (part.text === undefined ? [] : [part.text.text]))
- .join("\n");
- }
- const output = cursorToolOutput(toolCall);
- if (output === undefined) {
- return "";
- }
- return typeof output === "string" ? output : stableJson(output);
-}
-
-function cursorToolName(toolCall: ToolCall): string {
- if (toolCall.type !== "mcp") {
- return toolCall.type;
- }
- const provider = toolCall.args.providerIdentifier ?? "mcp";
- const tool = toolCall.args.toolName ?? "unknown";
- return `mcp__${provider}__${tool}`;
-}
-
-function cursorToolFileName(toolCall: ToolCall): string {
- switch (toolCall.type) {
- case "write":
- case "delete":
- case "read":
- case "edit":
- case "ls":
- return toolCall.args.path;
- case "generateImage":
- return toolCall.args.filePath ?? "generated-image";
- default:
- return cursorToolName(toolCall);
- }
-}
-
-function cursorToolSearchPattern(toolCall: ToolCall): string | undefined {
- switch (toolCall.type) {
- case "glob":
- return toolCall.args.globPattern;
- case "grep":
- return toolCall.args.pattern;
- case "semSearch":
- return toolCall.args.query;
- case "ls":
- return toolCall.args.path;
- case "readLints":
- return toolCall.args.paths.join(", ");
- default:
- return undefined;
- }
-}
-
-type CursorLsDirectoryNode = Extract<
- Extract["result"],
- { readonly status: "success" }
->["value"]["directoryTreeRoot"];
-
-function cursorLsSearchResults(
- node: CursorLsDirectoryNode,
- path: Path.Path,
-): ReadonlyArray<{ readonly fileName: string }> {
- return [
- ...node.childrenFiles.map((file) => ({ fileName: path.join(node.absPath, file.name) })),
- ...node.childrenDirs.flatMap((child) => cursorLsSearchResults(child, path)),
- ];
-}
-
-function cursorToolSearchResults(
- toolCall: ToolCall,
- path: Path.Path,
-): ReadonlyArray<{
- readonly fileName: string;
- readonly line?: number;
- readonly column?: number;
- readonly preview?: string;
-}> {
- if (toolCall.result?.status !== "success") {
- return [];
- }
- switch (toolCall.type) {
- case "glob":
- return toolCall.result.value.files.map((fileName) => ({ fileName }));
- case "grep":
- return Object.values(toolCall.result.value.workspaceResults ?? {}).flatMap((result) => {
- if (result.type === "files") {
- return result.output.files.map((fileName) => ({ fileName }));
- }
- if (result.type === "count") {
- return result.output.counts.map((entry) => ({
- fileName: entry.file,
- preview: `${entry.count} matches`,
- }));
- }
- return result.output.matches.map((entry) => ({
- fileName: entry.file,
- ...(entry.lineNumber === undefined ? {} : { line: entry.lineNumber }),
- preview: entry.line,
- }));
- });
- case "ls":
- return cursorLsSearchResults(toolCall.result.value.directoryTreeRoot, path);
- case "readLints":
- return toolCall.result.value.fileDiagnostics.flatMap((file) =>
- file.diagnostics.map((diagnostic) => {
- const line = diagnostic.range?.start?.line;
- const character = diagnostic.range?.start?.character;
- const hasLine = typeof line === "number" && Number.isInteger(line) && line >= 0;
- return {
- fileName: file.path,
- ...(hasLine ? { line: line + 1 } : {}),
- ...(hasLine &&
- typeof character === "number" &&
- Number.isInteger(character) &&
- character >= 0
- ? { column: character + 1 }
- : {}),
- preview: diagnostic.message,
- };
- }),
- );
- case "semSearch":
- return [
- {
- fileName: "semantic-search",
- preview: toolCall.result.value.results,
- },
- ];
- default:
- return [];
- }
-}
-
-function cursorTodoSteps(
- toolCall: Extract,
-): ReadonlyArray {
- const todos =
- toolCall.result?.status === "success" ? toolCall.result.value.todos : toolCall.args.todos;
- return todos
- .filter((todo) => todo.status !== "cancelled" && todo.content.trim().length > 0)
- .map((todo, index) => ({
- id: `todo-${index + 1}`,
- text: todo.content,
- status:
- todo.status === "inProgress"
- ? "running"
- : todo.status === "completed"
- ? "completed"
- : "pending",
- }));
-}
-
-function assistantTextsFromConversationSteps(steps: ReadonlyArray): ReadonlyArray {
- return steps.flatMap((step) => {
- const record = unknownRecord(step);
- if (record?.type === "assistantMessage") {
- const text = nestedString(record, ["message", "text"]);
- return text === undefined || text.length === 0 ? [] : [text];
- }
- const text = nestedString(record, ["assistantMessage", "text"]);
- return text === undefined || text.length === 0 ? [] : [text];
- });
-}
-
-function nestedToolResult>(
- rawResult: unknown,
- mapSuccess: (success: Record) => Value = (success) => success as Value,
-):
- | { readonly status: "success"; readonly value: Value }
- | { readonly status: "error"; readonly error: unknown }
- | undefined {
- const result = unknownRecord(rawResult);
- if (result === undefined) {
- return undefined;
- }
- const success = unknownRecord(result.success);
- if (success !== undefined) {
- return {
- status: "success",
- value: mapSuccess(success),
- };
- }
- return {
- status: "error",
- error: result.error ?? result.permissionDenied ?? result,
- };
-}
-
-function nestedGrepWorkspaceResults(success: Record): Record {
- const workspaces = unknownRecord(success.workspaceResults);
- if (workspaces === undefined) {
- return {};
- }
- return Object.fromEntries(
- Object.entries(workspaces).map(([workspace, rawWorkspaceResult]) => {
- const workspaceResult = unknownRecord(rawWorkspaceResult);
- const content = unknownRecord(workspaceResult?.content);
- const rawMatches = Array.isArray(content?.matches) ? content.matches : [];
- const matches = rawMatches.flatMap((rawFileMatch) => {
- const fileMatch = unknownRecord(rawFileMatch);
- const file = typeof fileMatch?.file === "string" ? fileMatch.file : "";
- const lineMatches = Array.isArray(fileMatch?.matches) ? fileMatch.matches : [];
- return lineMatches.flatMap((rawLineMatch) => {
- const lineMatch = unknownRecord(rawLineMatch);
- if (lineMatch === undefined) {
- return [];
- }
- const line = typeof lineMatch.content === "string" ? lineMatch.content : undefined;
- if (file.length === 0 || line === undefined) {
- return [];
- }
- return [
- {
- file,
- line,
- ...(typeof lineMatch.lineNumber === "number"
- ? { lineNumber: lineMatch.lineNumber }
- : {}),
- },
- ];
- });
- });
- return [
- workspace,
- {
- type: "content",
- output: {
- matches,
- totalMatches:
- typeof content?.totalMatchedLines === "number"
- ? content.totalMatchedLines
- : matches.length,
- },
- },
- ];
- }),
- );
-}
-
-export function nestedToolCallFromEnvelope(
- envelope: Record,
-): { readonly callId: string; readonly toolCall: ToolCall } | undefined {
- const callId = typeof envelope.toolCallId === "string" ? envelope.toolCallId : undefined;
- const wrapperEntry = Object.entries(envelope).find(([key]) => key.endsWith("ToolCall"));
- if (wrapperEntry === undefined) {
- return undefined;
- }
- const [wrapperName, rawCall] = wrapperEntry;
- const call = unknownRecord(rawCall);
- if (call === undefined) {
- return undefined;
- }
- const args = unknownRecord(call.args) ?? {};
- const fallbackCallId = `nested-${wrapperName}`;
- const nestedCallId = callId ?? fallbackCallId;
-
- switch (wrapperName) {
- case "readToolCall": {
- const success = unknownRecord(unknownRecord(call.result)?.success);
- const path =
- typeof args.path === "string"
- ? args.path
- : typeof success?.path === "string"
- ? success.path
- : "";
- return {
- callId: nestedCallId,
- toolCall: {
- type: "read",
- args: { path },
- result: nestedToolResult(call.result, (value) => ({
- content: typeof value.content === "string" ? value.content : "",
- totalLines: typeof value.totalLines === "number" ? value.totalLines : 0,
- fileSize: typeof value.fileSize === "number" ? value.fileSize : 0,
- })),
- } as unknown as ToolCall,
- };
- }
- case "globToolCall":
- return {
- callId: nestedCallId,
- toolCall: {
- type: "glob",
- args: {
- globPattern: typeof args.globPattern === "string" ? args.globPattern : "**/*",
- ...(typeof args.targetDirectory === "string"
- ? { targetDirectory: args.targetDirectory }
- : {}),
- },
- result: nestedToolResult(call.result, (value) => ({
- files: Array.isArray(value.files)
- ? value.files.filter((file): file is string => typeof file === "string")
- : [],
- totalFiles: typeof value.totalFiles === "number" ? value.totalFiles : 0,
- clientTruncated: false,
- ripgrepTruncated: false,
- })),
- } as unknown as ToolCall,
- };
- case "grepToolCall":
- return {
- callId: nestedCallId,
- toolCall: {
- type: "grep",
- args: {
- pattern: typeof args.pattern === "string" ? args.pattern : "",
- ...(typeof args.path === "string" ? { path: args.path } : {}),
- ...(typeof args.glob === "string" ? { glob: args.glob } : {}),
- ...(typeof args.outputMode === "string" ? { outputMode: args.outputMode } : {}),
- ...(typeof args.caseInsensitive === "boolean"
- ? { caseInsensitive: args.caseInsensitive }
- : {}),
- ...(typeof args.offset === "number" ? { offset: args.offset } : {}),
- ...(typeof args.multiline === "boolean" ? { multiline: args.multiline } : {}),
- },
- result: nestedToolResult(call.result, (value) => ({
- workspaceResults: nestedGrepWorkspaceResults(value),
- })),
- } as unknown as ToolCall,
- };
- case "shellToolCall": {
- const rawResult = unknownRecord(call.result);
- const permissionDenied = unknownRecord(rawResult?.permissionDenied);
- const command =
- typeof args.command === "string"
- ? args.command
- : typeof permissionDenied?.command === "string"
- ? permissionDenied.command
- : "";
- return {
- callId: nestedCallId,
- toolCall: {
- type: "shell",
- args: { command },
- result: nestedToolResult(call.result, (value) => ({
- stdout: typeof value.stdout === "string" ? value.stdout : "",
- stderr: typeof value.stderr === "string" ? value.stderr : "",
- exitCode: typeof value.exitCode === "number" ? value.exitCode : 0,
- signal: typeof value.signal === "string" ? value.signal : "",
- executionTime: typeof value.executionTime === "number" ? value.executionTime : 0,
- })),
- } as unknown as ToolCall,
- };
- }
- default: {
- const type = wrapperName.slice(0, -"ToolCall".length);
- return {
- callId: nestedCallId,
- toolCall: {
- type,
- args,
- result: nestedToolResult(call.result),
- } as unknown as ToolCall,
- };
- }
- }
-}
-
-function toolCallsFromConversationSteps(
- steps: ReadonlyArray,
-): ReadonlyArray<{ readonly callId: string; readonly toolCall: ToolCall }> {
- return steps.flatMap((step) => {
- const record = unknownRecord(step);
- if (record?.type === "toolCall") {
- const message = unknownRecord(record.message);
- return typeof message?.type === "string"
- ? [
- {
- callId: typeof record.callId === "string" ? record.callId : `nested-${message.type}`,
- toolCall: message as ToolCall,
- },
- ]
- : [];
- }
- const envelope = unknownRecord(record?.toolCall);
- const nested = envelope === undefined ? undefined : nestedToolCallFromEnvelope(envelope);
- return nested === undefined ? [] : [nested];
- });
-}
-
-function textFromAgentMessage(message: AgentMessage): string {
- const value = message.message;
- if (typeof value === "string") {
- return value;
- }
- const direct =
- nestedString(value, ["text"]) ??
- nestedString(value, ["message", "text"]) ??
- nestedString(value, ["userMessage", "text"]) ??
- nestedString(value, ["assistantMessage", "text"]);
- if (direct !== undefined) {
- return direct;
- }
- const content = unknownRecord(value)?.content;
- if (!Array.isArray(content)) {
- return "";
- }
- return content
- .flatMap((part) => {
- const text = unknownRecord(part)?.text;
- return typeof text === "string" ? [text] : [];
- })
- .join("");
-}
-
-interface CursorProjectionTarget {
- readonly threadId: ThreadId;
- readonly runId: ProviderAdapter.ProviderAdapterV2TurnInput["runId"] | null;
- readonly rootNodeId: OrchestrationV2ExecutionNode["rootNodeId"];
- readonly parentNodeId: OrchestrationV2ExecutionNode["id"];
- readonly providerThreadId: OrchestrationV2ProviderThread["id"] | null;
- readonly providerTurnId: OrchestrationV2ProviderTurn["id"] | null;
-}
-
-interface ActiveCursorToolCall {
- readonly callId: string;
- toolCall: ToolCall;
- readonly target: CursorProjectionTarget;
- readonly ordinal: number;
- readonly startedAt: DateTime.Utc;
- streamedOutput: string;
-}
-
-interface ActiveCursorSubagent {
- task: OrchestrationV2Subagent;
- toolCall: Extract;
- readonly callId: string;
- readonly childThreadId: ThreadId;
- readonly childRootNodeId: OrchestrationV2ExecutionNode["id"];
- readonly turnItemId: OrchestrationV2TurnItem["id"];
- readonly turnItemOrdinal: number;
- nextChildOrdinal: number;
- resultProjected: boolean;
-}
-
-interface ActiveCursorTextSegment {
- readonly nativeItemId: string;
- readonly startedAt: DateTime.Utc;
- text: string;
-}
-
-interface ActiveCursorTextStream {
- current: ActiveCursorTextSegment | null;
- nextSegment: number;
-}
-
-interface ActiveCursorTurn {
- readonly input: ProviderAdapter.ProviderAdapterV2TurnInput;
- readonly run: CursorAgentSdk.CursorAgentSdkRun;
- readonly providerTurnId: OrchestrationV2ProviderTurn["id"];
- readonly startedAt: DateTime.Utc;
- readonly completed: Deferred.Deferred;
- // Item ordinals allocated in this turn. No later turn looks items up here.
- readonly itemOrdinals: Map;
- readonly tools: Map;
- readonly subagents: Map;
- readonly assistant: ActiveCursorTextStream;
- readonly assistantReply: CursorTransportFailure;
- readonly reasoning: ActiveCursorTextStream;
- interrupted: boolean;
- finalized: boolean;
-}
-
-interface CursorLiveAgent {
- readonly nativeThreadId: string;
- readonly session: CursorAgentSdk.CursorAgentSdkSession;
-}
-
-export interface CursorAdapterV2Options {
- readonly instanceId: ProviderInstanceId;
- readonly settings: CursorSettings;
- readonly environment: NodeJS.ProcessEnv;
- readonly fileSystem: FileSystem.FileSystem;
- readonly path: Path.Path;
- readonly idAllocator: IdAllocator.IdAllocatorV2Shape;
- readonly runner: CursorAgentSdk.CursorAgentSdkRunnerShape;
- readonly serverConfig: ServerConfig.ServerConfig["Service"];
-}
-
-export function makeCursorAdapterV2(
- adapterOptions: CursorAdapterV2Options,
-): ProviderAdapter.ProviderAdapterV2Shape {
- const { fileSystem, path, idAllocator, runner, serverConfig } = adapterOptions;
- const apiKey = adapterOptions.environment.CURSOR_API_KEY?.trim() || undefined;
-
- return ProviderAdapter.ProviderAdapterV2.of({
- instanceId: adapterOptions.instanceId,
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- getCapabilities: () => Effect.succeed(CursorProviderCapabilitiesV2),
- planSelectionTransition: () => Effect.succeed(turnScopedSelectionTransition()),
- openSession: Effect.fn("CursorAdapterV2.openSession")(
- function* (input: ProviderAdapter.ProviderAdapterV2OpenSessionInput) {
- const sessionScope = yield* Effect.scope;
- const createdAt = yield* DateTime.now;
- const session = providerSession({
- providerSessionId: input.providerSessionId,
- providerInstanceId: adapterOptions.instanceId,
- cwd: input.runtimePolicy.cwd,
- model: input.modelSelection.model,
- now: createdAt,
- });
- const events = yield* Queue.unbounded();
- const liveAgent = yield* Ref.make(null);
- const activeTurn = yield* Ref.make(null);
- const planIds = yield* Ref.make(new Map());
-
- const emitProviderEvent = (event: ProviderAdapter.ProviderAdapterV2Event) =>
- Queue.offer(events, event).pipe(Effect.asVoid);
-
- const resolveItemOrdinal = (context: ActiveCursorTurn, nativeItemId: string) =>
- Effect.sync(() => {
- const existing = context.itemOrdinals.get(nativeItemId);
- if (existing !== undefined) {
- return existing;
- }
- const ordinal = context.input.providerTurnOrdinal * 100 + context.itemOrdinals.size + 1;
- context.itemOrdinals.set(nativeItemId, ordinal);
- return ordinal;
- });
-
- const resolvePlanId = Effect.fnUntraced(function* (
- context: ActiveCursorTurn,
- nativeItemId: string,
- ) {
- const existing = (yield* Ref.get(planIds)).get(nativeItemId);
- if (existing !== undefined) {
- return existing;
- }
- const planId = yield* idAllocator.allocate.plan({
- threadId: context.input.threadId,
- runId: context.input.runId,
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- });
- yield* Ref.update(planIds, (current) => {
- const updated = new Map(current);
- updated.set(nativeItemId, planId);
- return updated;
- });
- return planId;
- });
-
- const parentTarget = (context: ActiveCursorTurn): CursorProjectionTarget => ({
- threadId: context.input.threadId,
- runId: context.input.runId,
- rootNodeId: context.input.rootNodeId,
- parentNodeId: context.input.rootNodeId,
- providerThreadId: context.input.providerThread.id,
- providerTurnId: context.providerTurnId,
- });
-
- const emitAssistant = Effect.fnUntraced(function* (
- context: ActiveCursorTurn,
- completed: boolean,
- ) {
- const segment = context.assistant.current;
- if (segment === null || segment.text.length === 0) {
- return;
- }
- const now = yield* DateTime.now;
- const ordinal = yield* resolveItemOrdinal(context, segment.nativeItemId);
- const nodeId = idAllocator.derive.nodeFromProviderItem({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeItemId: segment.nativeItemId,
- });
- const messageId = idAllocator.derive.messageFromProviderItem({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeItemId: segment.nativeItemId,
- });
- const turnItemId = idAllocator.derive.turnItemFromProviderItem({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeItemId: segment.nativeItemId,
- });
- const nativeItemRef = {
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeId: segment.nativeItemId,
- strength: "weak" as const,
- };
- yield* emitProviderEvent({
- type: "node.updated",
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- node: {
- id: nodeId,
- threadId: context.input.threadId,
- runId: context.input.runId,
- parentNodeId: context.input.rootNodeId,
- rootNodeId: context.input.rootNodeId,
- kind: "assistant_message",
- status: completed ? "completed" : "running",
- countsForRun: false,
- providerThreadId: context.input.providerThread.id,
- providerTurnId: context.providerTurnId,
- nativeItemRef,
- runtimeRequestId: null,
- checkpointScopeId: null,
- startedAt: segment.startedAt,
- completedAt: completed ? now : null,
- },
- });
- yield* emitProviderEvent({
- type: "message.updated",
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- message: {
- createdBy: "agent",
- creationSource: "provider",
- id: messageId,
- threadId: context.input.threadId,
- runId: context.input.runId,
- nodeId,
- role: "assistant",
- text: segment.text,
- attachments: [],
- streaming: !completed,
- createdAt: segment.startedAt,
- updatedAt: now,
- },
- });
- yield* emitProviderEvent({
- type: "turn_item.updated",
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- turnItem: {
- id: turnItemId,
- threadId: context.input.threadId,
- runId: context.input.runId,
- nodeId,
- providerThreadId: context.input.providerThread.id,
- providerTurnId: context.providerTurnId,
- nativeItemRef,
- parentItemId: null,
- ordinal,
- status: completed ? "completed" : "running",
- title: null,
- startedAt: segment.startedAt,
- completedAt: completed ? now : null,
- updatedAt: now,
- type: "assistant_message",
- messageId,
- text: segment.text,
- streaming: !completed,
- },
- });
- });
-
- const emitReasoning = Effect.fnUntraced(function* (
- context: ActiveCursorTurn,
- completed: boolean,
- ) {
- const segment = context.reasoning.current;
- if (segment === null || segment.text.length === 0) {
- return;
- }
- const now = yield* DateTime.now;
- const ordinal = yield* resolveItemOrdinal(context, segment.nativeItemId);
- const nodeId = idAllocator.derive.nodeFromProviderItem({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeItemId: segment.nativeItemId,
- });
- const turnItemId = idAllocator.derive.turnItemFromProviderItem({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeItemId: segment.nativeItemId,
- });
- const nativeItemRef = {
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeId: segment.nativeItemId,
- strength: "weak" as const,
- };
- yield* emitProviderEvent({
- type: "node.updated",
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- node: {
- id: nodeId,
- threadId: context.input.threadId,
- runId: context.input.runId,
- parentNodeId: context.input.rootNodeId,
- rootNodeId: context.input.rootNodeId,
- kind: "reasoning",
- status: completed ? "completed" : "running",
- countsForRun: false,
- providerThreadId: context.input.providerThread.id,
- providerTurnId: context.providerTurnId,
- nativeItemRef,
- runtimeRequestId: null,
- checkpointScopeId: null,
- startedAt: segment.startedAt,
- completedAt: completed ? now : null,
- },
- });
- yield* emitProviderEvent({
- type: "turn_item.updated",
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- turnItem: {
- id: turnItemId,
- threadId: context.input.threadId,
- runId: context.input.runId,
- nodeId,
- providerThreadId: context.input.providerThread.id,
- providerTurnId: context.providerTurnId,
- nativeItemRef,
- parentItemId: null,
- ordinal,
- status: completed ? "completed" : "running",
- title: null,
- startedAt: segment.startedAt,
- completedAt: completed ? now : null,
- updatedAt: now,
- type: "reasoning",
- text: segment.text,
- streaming: !completed,
- },
- });
- });
-
- const appendTextSegment = Effect.fnUntraced(function* (input: {
- readonly context: ActiveCursorTurn;
- readonly stream: ActiveCursorTextStream;
- readonly kind: "assistant" | "reasoning";
- readonly text: string;
- }) {
- if (input.stream.current === null) {
- input.stream.nextSegment += 1;
- input.stream.current = {
- nativeItemId: `${input.kind}:${input.context.run.runId}:${input.stream.nextSegment}`,
- startedAt: yield* DateTime.now,
- text: "",
- };
- }
- input.stream.current.text += input.text;
- });
-
- const completeAssistant = Effect.fnUntraced(function* (context: ActiveCursorTurn) {
- if (context.assistant.current === null) {
- return;
- }
- yield* emitAssistant(context, true);
- context.assistant.current = null;
- });
-
- const completeReasoning = Effect.fnUntraced(function* (context: ActiveCursorTurn) {
- if (context.reasoning.current === null) {
- return;
- }
- yield* emitReasoning(context, true);
- context.reasoning.current = null;
- });
-
- const emitToolArtifacts = Effect.fnUntraced(function* (input: {
- readonly active: ActiveCursorToolCall;
- readonly completed: boolean;
- /** Terminal status for a tool the turn ended before Cursor completed it. */
- readonly unfinishedStatus?: "interrupted" | "failed" | "cancelled";
- }) {
- const { active } = input;
- const toolCall = active.toolCall;
- const now = yield* DateTime.now;
- const failed = input.completed && cursorToolFailed(toolCall);
- const status = !input.completed
- ? "running"
- : (input.unfinishedStatus ?? (failed ? "failed" : "completed"));
- const nodeId = idAllocator.derive.nodeFromProviderItem({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeItemId: active.callId,
- });
- const turnItemId = idAllocator.derive.turnItemFromProviderItem({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeItemId: active.callId,
- });
- const nativeItemRef = {
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeId: active.callId,
- strength: "strong" as const,
- };
- const node: OrchestrationV2ExecutionNode = {
- id: nodeId,
- threadId: active.target.threadId,
- runId: active.target.runId,
- parentNodeId: active.target.parentNodeId,
- rootNodeId: active.target.rootNodeId,
- kind: "tool_call",
- status,
- countsForRun: false,
- providerThreadId: active.target.providerThreadId,
- providerTurnId: active.target.providerTurnId,
- nativeItemRef,
- runtimeRequestId: null,
- checkpointScopeId: null,
- startedAt: active.startedAt,
- completedAt: input.completed ? now : null,
- };
- const base = {
- id: turnItemId,
- threadId: active.target.threadId,
- runId: active.target.runId,
- nodeId,
- providerThreadId: active.target.providerThreadId,
- providerTurnId: active.target.providerTurnId,
- nativeItemRef,
- parentItemId: null,
- ordinal: active.ordinal,
- status,
- title: null,
- startedAt: active.startedAt,
- completedAt: input.completed ? now : null,
- updatedAt: now,
- } satisfies Pick<
- OrchestrationV2TurnItem,
- | "id"
- | "threadId"
- | "runId"
- | "nodeId"
- | "providerThreadId"
- | "providerTurnId"
- | "nativeItemRef"
- | "parentItemId"
- | "ordinal"
- | "status"
- | "title"
- | "startedAt"
- | "completedAt"
- | "updatedAt"
- >;
- const outputText = cursorToolOutputText(toolCall) || active.streamedOutput;
- let turnItem: OrchestrationV2TurnItem;
- switch (toolCall.type) {
- case "shell":
- turnItem = {
- ...base,
- type: "command_execution",
- input: toolCall.args.command,
- ...(outputText.length === 0 ? {} : { output: outputText }),
- ...(toolCall.result?.status === "success"
- ? { exitCode: toolCall.result.value.exitCode }
- : {}),
- };
- break;
- case "write":
- case "delete":
- case "edit":
- case "generateImage":
- turnItem = {
- ...base,
- type: "file_change",
- fileName: cursorToolFileName(toolCall),
- ...(toolCall.type === "edit" &&
- toolCall.result?.status === "success" &&
- toolCall.result.value.linesAdded !== undefined
- ? { additions: toolCall.result.value.linesAdded }
- : {}),
- ...(toolCall.type === "edit" &&
- toolCall.result?.status === "success" &&
- toolCall.result.value.linesRemoved !== undefined
- ? { deletions: toolCall.result.value.linesRemoved }
- : {}),
- ...(toolCall.type === "edit" &&
- toolCall.result?.status === "success" &&
- toolCall.result.value.diffString !== undefined
- ? { diffStr: toolCall.result.value.diffString }
- : // A failed change keeps its error where the diff would be.
- toolCall.result?.status === "error" && outputText.trim().length > 0
- ? { diffStr: outputText }
- : {}),
- ...(toolCall.type === "write" ? { newStr: toolCall.args.fileText } : {}),
- };
- break;
- case "read":
- turnItem = {
- ...base,
- title: formatReadToolLabel(toolCall.args.path),
- type: "dynamic_tool",
- toolName: "Read",
- input: toolCall.args,
- ...(cursorToolOutput(toolCall) === undefined
- ? {}
- : { output: cursorToolOutput(toolCall) }),
- };
- break;
- case "glob":
- case "grep":
- case "ls":
- case "readLints":
- case "semSearch": {
- const pattern = cursorToolSearchPattern(toolCall);
- const searchPath =
- toolCall.type === "grep"
- ? toolCall.args.path
- : toolCall.type === "glob"
- ? toolCall.args.targetDirectory
- : toolCall.type === "semSearch"
- ? toolCall.args.targetDirectories?.join(", ")
- : pattern;
- // A failed search keeps its error as one row under the searched path.
- const results =
- toolCall.result?.status === "error" && outputText.trim().length > 0
- ? [{ fileName: searchPath?.trim() || ".", preview: outputText }]
- : cursorToolSearchResults(toolCall, path);
- turnItem = {
- ...base,
- title:
- formatSearchToolLabel({
- input: toolCall.args,
- ...(pattern === undefined ? {} : { pattern }),
- }) ?? null,
- type: "file_search",
- ...(pattern === undefined ? {} : { pattern }),
- ...(results.length === 0 ? {} : { results: [...results] }),
- };
- break;
- }
- default:
- turnItem = {
- ...base,
- type: "dynamic_tool",
- ...(toolCall.type === "mcp"
- ? mcpToolPresentation({
- serverName: toolCall.args.providerIdentifier,
- toolName: toolCall.args.toolName,
- })
- : {}),
- toolName: cursorToolName(toolCall),
- input: toolCall.args,
- ...(cursorToolOutput(toolCall) === undefined
- ? {}
- : { output: cursorToolOutput(toolCall) }),
- };
- }
- yield* emitProviderEvent({
- type: "node.updated",
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- node,
- });
- yield* emitProviderEvent({
- type: "turn_item.updated",
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- turnItem,
- });
- });
-
- const ensureToolStarted = Effect.fnUntraced(function* (
- context: ActiveCursorTurn,
- callId: string,
- toolCall: ToolCall,
- ) {
- const existing = context.tools.get(callId);
- if (existing !== undefined) {
- existing.toolCall = toolCall;
- return existing;
- }
- const startedAt = yield* DateTime.now;
- const active: ActiveCursorToolCall = {
- callId,
- toolCall,
- target: parentTarget(context),
- ordinal: yield* resolveItemOrdinal(context, callId),
- startedAt,
- streamedOutput: "",
- };
- context.tools.set(callId, active);
- yield* emitToolArtifacts({ active, completed: false });
- return active;
- });
-
- const emitPlanArtifacts = Effect.fnUntraced(function* (input: {
- readonly context: ActiveCursorTurn;
- readonly callId: string;
- readonly markdown: string;
- readonly completed: boolean;
- readonly failed: boolean;
- }) {
- const nativeItemId = `plan:${input.callId}`;
- const now = yield* DateTime.now;
- const planId = yield* resolvePlanId(input.context, nativeItemId);
- const ordinal = yield* resolveItemOrdinal(input.context, nativeItemId);
- const nodeId = idAllocator.derive.nodeFromProviderItem({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeItemId,
- });
- const turnItemId = idAllocator.derive.turnItemFromProviderItem({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeItemId,
- });
- const status = input.failed ? "failed" : input.completed ? "completed" : "running";
- const nativeItemRef = {
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeId: input.callId,
- strength: "strong" as const,
- };
- const plan: OrchestrationV2PlanArtifact = {
- id: planId,
- threadId: input.context.input.threadId,
- runId: input.context.input.runId,
- nodeId,
- kind: "proposed_plan",
- status: input.completed ? "active" : "draft",
- markdown: input.markdown,
- };
- yield* emitProviderEvent({
- type: "node.updated",
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- node: {
- id: nodeId,
- threadId: input.context.input.threadId,
- runId: input.context.input.runId,
- parentNodeId: input.context.input.rootNodeId,
- rootNodeId: input.context.input.rootNodeId,
- kind: "plan",
- status,
- countsForRun: false,
- providerThreadId: input.context.input.providerThread.id,
- providerTurnId: input.context.providerTurnId,
- nativeItemRef,
- runtimeRequestId: null,
- checkpointScopeId: null,
- startedAt: input.context.startedAt,
- completedAt: input.completed ? now : null,
- },
- });
- yield* emitProviderEvent({
- type: "plan.updated",
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- plan,
- });
- yield* emitProviderEvent({
- type: "turn_item.updated",
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- turnItem: {
- id: turnItemId,
- threadId: input.context.input.threadId,
- runId: input.context.input.runId,
- nodeId,
- providerThreadId: input.context.input.providerThread.id,
- providerTurnId: input.context.providerTurnId,
- nativeItemRef,
- parentItemId: null,
- ordinal,
- status,
- title: null,
- startedAt: input.context.startedAt,
- completedAt: input.completed ? now : null,
- updatedAt: now,
- type: "proposed_plan",
- planId,
- markdown: input.markdown,
- streaming: !input.completed,
- },
- });
- });
-
- const emitTodoArtifacts = Effect.fnUntraced(function* (input: {
- readonly context: ActiveCursorTurn;
- readonly callId: string;
- readonly toolCall: Extract;
- readonly completed: boolean;
- readonly failed: boolean;
- }) {
- const nativeItemId = `todos:${input.callId}`;
- const now = yield* DateTime.now;
- const planId = yield* resolvePlanId(input.context, nativeItemId);
- const ordinal = yield* resolveItemOrdinal(input.context, nativeItemId);
- const nodeId = idAllocator.derive.nodeFromProviderItem({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeItemId,
- });
- const turnItemId = idAllocator.derive.turnItemFromProviderItem({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeItemId,
- });
- const status = input.failed ? "failed" : input.completed ? "completed" : "running";
- const steps = cursorTodoSteps(input.toolCall);
- const nativeItemRef = {
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeId: input.callId,
- strength: "strong" as const,
- };
- yield* emitProviderEvent({
- type: "node.updated",
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- node: {
- id: nodeId,
- threadId: input.context.input.threadId,
- runId: input.context.input.runId,
- parentNodeId: input.context.input.rootNodeId,
- rootNodeId: input.context.input.rootNodeId,
- kind: "todo_list",
- status,
- countsForRun: false,
- providerThreadId: input.context.input.providerThread.id,
- providerTurnId: input.context.providerTurnId,
- nativeItemRef,
- runtimeRequestId: null,
- checkpointScopeId: null,
- startedAt: input.context.startedAt,
- completedAt: input.completed ? now : null,
- },
- });
- yield* emitProviderEvent({
- type: "plan.updated",
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- plan: {
- id: planId,
- threadId: input.context.input.threadId,
- runId: input.context.input.runId,
- nodeId,
- kind: "todo_list",
- status: input.completed ? "active" : "draft",
- steps: [...steps],
- },
- });
- yield* emitProviderEvent({
- type: "turn_item.updated",
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- turnItem: {
- id: turnItemId,
- threadId: input.context.input.threadId,
- runId: input.context.input.runId,
- nodeId,
- providerThreadId: input.context.input.providerThread.id,
- providerTurnId: input.context.providerTurnId,
- nativeItemRef,
- parentItemId: null,
- ordinal,
- status,
- title: null,
- startedAt: input.context.startedAt,
- completedAt: input.completed ? now : null,
- updatedAt: now,
- type: "todo_list",
- planId,
- steps: [...steps],
- },
- });
- });
-
- const emitSubagent = Effect.fnUntraced(function* (input: {
- readonly context: ActiveCursorTurn;
- readonly callId: string;
- readonly toolCall: Extract;
- readonly completed: boolean;
- readonly status?: OrchestrationV2Subagent["status"];
- }) {
- const args = input.toolCall.args;
- const result =
- input.toolCall.result?.status === "success" ? input.toolCall.result.value : undefined;
- const existing = input.context.subagents.get(input.callId);
- if (
- existing !== undefined &&
- !isOrchestrationV2WorkActive(existing.task.status) &&
- !input.completed
- )
- return;
- const now = yield* DateTime.now;
- const status: OrchestrationV2Subagent["status"] =
- input.status ??
- (input.completed
- ? cursorToolFailed(input.toolCall)
- ? "failed"
- : "completed"
- : "running");
- const resultText = [
- ...assistantTextsFromConversationSteps(result?.conversationSteps ?? []),
- ...(result?.resultSuffix === undefined ? [] : [result.resultSuffix]),
- ]
- .filter((part) => part.trim().length > 0)
- .join("\n");
- const nativeItemId = input.callId;
- const nodeId =
- existing?.task.id ??
- idAllocator.derive.nodeFromProviderItem({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeItemId,
- });
- const childRootNodeId =
- existing?.childRootNodeId ??
- idAllocator.derive.nodeFromProviderItem({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeItemId: `${nativeItemId}:child-root`,
- });
- const childThreadId =
- existing?.childThreadId ??
- idAllocator.derive.threadFromProviderThread({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeThreadId: `${input.context.run.runId}:task:${input.callId}`,
- });
- const task: OrchestrationV2Subagent = {
- ...(existing?.task ?? {
- id: nodeId,
- threadId: input.context.input.threadId,
- runId: input.context.input.runId,
- parentNodeId: input.context.input.rootNodeId,
- origin: "provider_native" as const,
- createdBy: "agent" as const,
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- providerInstanceId: input.context.input.modelSelection.instanceId,
- providerThreadId: null,
- childThreadId,
- nativeTaskRef: {
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeId: input.callId,
- strength: "strong" as const,
- },
- prompt: args.prompt,
- title: args.description,
- result: null,
- startedAt: now,
- }),
- model: args.model?.trim() || existing?.task.model || null,
- nativeTaskRef: {
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeId: input.callId,
- strength: "strong" as const,
- },
- status,
- result: resultText.length === 0 ? (existing?.task.result ?? null) : resultText,
- completedAt: input.completed ? (existing?.task.completedAt ?? now) : null,
- updatedAt: now,
- };
- const subagent: ActiveCursorSubagent = {
- task,
- toolCall: input.toolCall,
- callId: input.callId,
- childThreadId,
- childRootNodeId,
- turnItemId:
- existing?.turnItemId ??
- idAllocator.derive.turnItemFromProviderItem({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeItemId,
- }),
- turnItemOrdinal:
- existing?.turnItemOrdinal ?? (yield* resolveItemOrdinal(input.context, nativeItemId)),
- nextChildOrdinal: existing?.nextChildOrdinal ?? 100,
- resultProjected: existing?.resultProjected ?? false,
- };
- input.context.subagents.set(input.callId, subagent);
-
- if (existing === undefined) {
- yield* emitProviderEvent({
- type: "app_thread.created",
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- appThread: makeSubagentChildThread({
- parentThread: input.context.input.appThread,
- childThreadId,
- parentNodeId: nodeId,
- activeProviderThreadId: null,
- providerInstanceId: input.context.input.modelSelection.instanceId,
- modelSelection: input.context.input.modelSelection,
- title: subagentThreadTitle({
- parentTitle: input.context.input.appThread.title,
- title: args.description,
- prompt: args.prompt,
- ordinal: input.context.subagents.size,
- }),
- now,
- createdBy: "agent",
- creationSource: "provider",
- }),
- });
- const promptNativeId = `${nativeItemId}:prompt`;
- const promptArtifacts = makeSubagentConversationArtifacts({
- senderThreadId: input.context.input.threadId,
- messageId: idAllocator.derive.messageFromProviderItem({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeItemId: promptNativeId,
- }),
- turnItemId: idAllocator.derive.turnItemFromProviderItem({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeItemId: promptNativeId,
- }),
- threadId: childThreadId,
- rootNodeId: childRootNodeId,
- providerThreadId: null,
- providerTurnId: null,
- nativeItemRef: {
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeId: promptNativeId,
- strength: "weak",
- },
- role: "user",
- text: args.prompt,
- ordinal: 100,
- now,
- });
- yield* emitProviderEvent({
- type: "message.updated",
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- message: promptArtifacts.message,
- });
- yield* emitProviderEvent({
- type: "turn_item.updated",
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- turnItem: promptArtifacts.turnItem,
- });
- }
-
- yield* emitProviderEvent({
- type: "node.updated",
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- node: {
- id: nodeId,
- threadId: input.context.input.threadId,
- runId: input.context.input.runId,
- parentNodeId: input.context.input.rootNodeId,
- rootNodeId: input.context.input.rootNodeId,
- kind: "subagent",
- status,
- countsForRun: false,
- providerThreadId: input.context.input.providerThread.id,
- providerTurnId: input.context.providerTurnId,
- nativeItemRef: task.nativeTaskRef,
- runtimeRequestId: null,
- checkpointScopeId: null,
- startedAt: task.startedAt,
- completedAt: task.completedAt,
- },
- });
- yield* emitProviderEvent({
- type: "node.updated",
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- node: {
- id: childRootNodeId,
- threadId: childThreadId,
- runId: null,
- parentNodeId: null,
- rootNodeId: childRootNodeId,
- kind: "root_turn",
- status,
- countsForRun: false,
- providerThreadId: null,
- providerTurnId: null,
- nativeItemRef: task.nativeTaskRef,
- runtimeRequestId: null,
- checkpointScopeId: null,
- startedAt: task.startedAt,
- completedAt: task.completedAt,
- },
- });
- yield* emitProviderEvent({
- type: "subagent.updated",
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- subagent: task,
- });
- yield* emitProviderEvent({
- type: "turn_item.updated",
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- turnItem: {
- id: subagent.turnItemId,
- threadId: input.context.input.threadId,
- runId: input.context.input.runId,
- nodeId,
- providerThreadId: input.context.input.providerThread.id,
- providerTurnId: input.context.providerTurnId,
- nativeItemRef: task.nativeTaskRef,
- parentItemId: null,
- ordinal: subagent.turnItemOrdinal,
- status,
- title: task.title,
- startedAt: task.startedAt,
- completedAt: task.completedAt,
- updatedAt: now,
- type: "subagent",
- subagentId: task.id,
- origin: task.origin,
- driver: task.driver,
- providerInstanceId: task.providerInstanceId,
- childThreadId,
- prompt: task.prompt,
- result: task.result,
- },
- });
-
- if (input.completed && !subagent.resultProjected) {
- for (const [index, nestedTool] of toolCallsFromConversationSteps(
- result?.conversationSteps ?? [],
- ).entries()) {
- const callId = `${nativeItemId}:child-tool:${nestedTool.callId || index + 1}`;
- const startedAt = task.startedAt ?? now;
- const active: ActiveCursorToolCall = {
- callId,
- toolCall: nestedTool.toolCall,
- target: {
- threadId: childThreadId,
- runId: null,
- rootNodeId: childRootNodeId,
- parentNodeId: childRootNodeId,
- providerThreadId: null,
- providerTurnId: null,
- },
- ordinal: ++subagent.nextChildOrdinal,
- startedAt,
- streamedOutput: "",
- };
- yield* emitToolArtifacts({ active, completed: true });
- }
- if (task.result !== null && task.result.length > 0) {
- const resultNativeId = `${nativeItemId}:result`;
- const resultArtifacts = makeSubagentConversationArtifacts({
- messageId: idAllocator.derive.messageFromProviderItem({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeItemId: resultNativeId,
- }),
- turnItemId: idAllocator.derive.turnItemFromProviderItem({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeItemId: resultNativeId,
- }),
- threadId: childThreadId,
- rootNodeId: childRootNodeId,
- providerThreadId: null,
- providerTurnId: null,
- nativeItemRef: {
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeId: resultNativeId,
- strength: "weak",
- },
- role: "assistant",
- text: task.result,
- ordinal: ++subagent.nextChildOrdinal,
- now,
- });
- yield* emitProviderEvent({
- type: "message.updated",
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- message: resultArtifacts.message,
- });
- yield* emitProviderEvent({
- type: "turn_item.updated",
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- turnItem: resultArtifacts.turnItem,
- });
- }
- subagent.resultProjected = true;
- }
- });
-
- const handleToolUpdate = Effect.fnUntraced(function* (
- context: ActiveCursorTurn,
- update: Extract<
- InteractionUpdate,
- {
- readonly type: "tool-call-started" | "partial-tool-call" | "tool-call-completed";
- }
- >,
- ) {
- const completed = update.type === "tool-call-completed";
- const toolCall = update.toolCall;
- switch (toolCall.type) {
- case "createPlan":
- yield* emitPlanArtifacts({
- context,
- callId: update.callId,
- markdown: toolCall.args.plan,
- completed,
- failed: completed && cursorToolFailed(toolCall),
- });
- return;
- case "updateTodos":
- yield* emitTodoArtifacts({
- context,
- callId: update.callId,
- toolCall,
- completed,
- failed: completed && cursorToolFailed(toolCall),
- });
- return;
- case "task":
- if (update.type === "partial-tool-call") {
- return;
- }
- yield* emitSubagent({
- context,
- callId: update.callId,
- toolCall,
- completed,
- });
- return;
- default: {
- const active = yield* ensureToolStarted(context, update.callId, toolCall);
- active.toolCall = toolCall;
- if (update.type !== "tool-call-started") {
- yield* emitToolArtifacts({ active, completed });
- }
- if (completed) {
- context.tools.delete(update.callId);
- }
- }
- }
- });
-
- const shellOutputText = (event: Record): string => {
- const candidates = [
- event.text,
- event.output,
- event.data,
- event.stdout,
- event.stderr,
- event.chunk,
- ];
- return candidates.filter((value): value is string => typeof value === "string").join("");
- };
-
- const handleInteractionUpdate = Effect.fnUntraced(function* (
- context: ActiveCursorTurn,
- update: InteractionUpdate,
- ) {
- if (context.finalized) {
- return;
- }
- switch (update.type) {
- case "text-delta":
- context.assistantReply.push(update.text);
- yield* completeReasoning(context);
- yield* appendTextSegment({
- context,
- stream: context.assistant,
- kind: "assistant",
- text: update.text,
- });
- yield* emitAssistant(context, false);
- return;
- case "thinking-delta":
- yield* completeAssistant(context);
- yield* appendTextSegment({
- context,
- stream: context.reasoning,
- kind: "reasoning",
- text: update.text,
- });
- yield* emitReasoning(context, false);
- return;
- case "thinking-completed":
- yield* completeReasoning(context);
- return;
- case "tool-call-started":
- case "partial-tool-call":
- case "tool-call-completed":
- yield* completeAssistant(context);
- yield* completeReasoning(context);
- yield* handleToolUpdate(context, update);
- return;
- case "step-completed":
- case "turn-ended":
- yield* completeAssistant(context);
- yield* completeReasoning(context);
- return;
- case "shell-output-delta": {
- const shell = Array.from(context.tools.values())
- .toReversed()
- .find((candidate) => candidate.toolCall.type === "shell");
- if (shell === undefined) {
- return;
- }
- shell.streamedOutput += shellOutputText(update.event);
- yield* emitToolArtifacts({ active: shell, completed: false });
- return;
- }
- default:
- return;
- }
- });
-
- const providerTurnPayload = (input: {
- readonly context: ActiveCursorTurn;
- readonly status: OrchestrationV2ProviderTurn["status"];
- readonly completedAt: DateTime.Utc | null;
- }): OrchestrationV2ProviderTurn => ({
- id: input.context.providerTurnId,
- providerThreadId: input.context.input.providerThread.id,
- nodeId: input.context.input.rootNodeId,
- runAttemptId: input.context.input.attemptId,
- nativeTurnRef: {
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeId: input.context.run.runId,
- strength: "strong",
- },
- ordinal: input.context.input.providerTurnOrdinal,
- status: input.status,
- startedAt: input.context.startedAt,
- completedAt: input.completedAt,
- });
-
- const finalizeTurn = Effect.fnUntraced(function* (input: {
- readonly context: ActiveCursorTurn;
- readonly status: Extract<
- OrchestrationV2ProviderTurn["status"],
- "completed" | "interrupted" | "failed" | "cancelled"
- >;
- readonly failure?: OrchestrationV2ProviderFailure;
- readonly threadDisposition?: "reusable" | "broken";
- }) {
- if (input.context.finalized) {
- return;
- }
- input.context.finalized = true;
- const completedAt = yield* DateTime.now;
- // Tools still here never got a tool-call-completed. A stopped or
- // failed turn cut them short, so they end with the turn's status.
- for (const tool of input.context.tools.values()) {
- yield* emitToolArtifacts({
- active: tool,
- completed: true,
- ...(input.status === "completed" ? {} : { unfinishedStatus: input.status }),
- });
- }
- input.context.tools.clear();
- // This interaction stops delivering updates at finalization. Tasks
- // without a completion have an unknown outcome. Background launch
- // acknowledgements have already settled their rows.
- for (const subagent of input.context.subagents.values()) {
- if (!isOrchestrationV2WorkActive(subagent.task.status)) continue;
- yield* emitSubagent({
- context: input.context,
- callId: subagent.callId,
- toolCall: subagent.toolCall,
- completed: true,
- status: input.status === "completed" ? "idle" : input.status,
- });
- }
- yield* completeReasoning(input.context);
- yield* completeAssistant(input.context);
- yield* emitProviderEvent({
- type: "provider_turn.updated",
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- providerTurn: providerTurnPayload({
- context: input.context,
- status: input.status,
- completedAt,
- }),
- });
- yield* emitProviderEvent({
- type: "provider_thread.updated",
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- providerThread: {
- ...input.context.input.providerThread,
- providerSessionId: session.id,
- status: "active",
- firstRunOrdinal:
- input.context.input.providerThread.firstRunOrdinal ??
- input.context.input.runOrdinal,
- lastRunOrdinal: input.context.input.runOrdinal,
- updatedAt: completedAt,
- },
- });
- const threadDisposition = input.threadDisposition ?? "reusable";
- yield* emitProviderEvent(
- input.status === "failed"
- ? {
- type: "turn.terminal",
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- providerThreadId: input.context.input.providerThread.id,
- providerTurnId: input.context.providerTurnId,
- runOrdinal: input.context.input.runOrdinal,
- failureItemOrdinal: yield* resolveItemOrdinal(
- input.context,
- `terminal-failure:${input.context.providerTurnId}`,
- ),
- status: input.status,
- failure: input.failure ?? makeProviderFailure({ class: "provider_error" }),
- threadDisposition,
- }
- : {
- type: "turn.terminal",
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- providerThreadId: input.context.input.providerThread.id,
- providerTurnId: input.context.providerTurnId,
- runOrdinal: input.context.input.runOrdinal,
- status: input.status,
- failure: null,
- threadDisposition,
- },
- );
- yield* Ref.update(activeTurn, (current) =>
- current?.providerTurnId === input.context.providerTurnId ? null : current,
- );
- yield* Deferred.succeed(input.context.completed, undefined);
- });
-
- const terminalStatus = (
- context: ActiveCursorTurn,
- result: RunResult,
- ): Extract<
- OrchestrationV2ProviderTurn["status"],
- "completed" | "interrupted" | "failed" | "cancelled"
- > => {
- if (context.interrupted) {
- return "interrupted";
- }
- switch (result.status) {
- case "finished":
- return "completed";
- case "cancelled":
- return "cancelled";
- case "error":
- return "failed";
- }
- };
-
- const openAgent = Effect.fnUntraced(function* (openInput: {
- readonly operation: "create" | "resume";
- readonly threadId: ThreadId;
- readonly modelSelection: ModelSelection;
- readonly runtimePolicy: ProviderAdapter.ProviderAdapterV2RuntimePolicy;
- readonly agentId?: string;
- }) {
- const existing = yield* Ref.get(liveAgent);
- if (
- existing !== null &&
- openInput.operation === "resume" &&
- existing.nativeThreadId === openInput.agentId
- ) {
- return existing;
- }
- if (existing !== null) {
- yield* existing.session.close.pipe(Effect.ignore);
- yield* Ref.set(liveAgent, null);
- }
- const sdkSession = yield* runner.open({
- operation: openInput.operation,
- ...(openInput.agentId === undefined ? {} : { agentId: openInput.agentId }),
- options: makeCursorAgentOptions({
- ...(apiKey === undefined ? {} : { apiKey }),
- modelSelection: openInput.modelSelection,
- runtimePolicy: openInput.runtimePolicy,
- threadId: openInput.threadId,
- }),
- threadId: openInput.threadId,
- providerSessionId: input.providerSessionId,
- });
- const next = {
- nativeThreadId: sdkSession.agentId,
- session: sdkSession,
- } satisfies CursorLiveAgent;
- yield* Ref.set(liveAgent, next);
- return next;
- });
-
- let cursorSkillNames: ReadonlySet | undefined;
- const resolveUserMessage = Effect.fnUntraced(function* (
- turnInput: ProviderAdapter.ProviderAdapterV2TurnInput,
- ) {
- const rawText = turnInput.message.text;
- if (rawText.trim() === "/compress" && turnInput.message.attachments.length === 0) {
- return "/compress";
- }
- if (hasCursorSkillMention(rawText) && cursorSkillNames === undefined) {
- const skills = yield* discoverCursorSkills(
- turnInput.runtimePolicy.cwd ?? undefined,
- adapterOptions.environment,
- ).pipe(
- Effect.provideService(FileSystem.FileSystem, fileSystem),
- Effect.provideService(Path.Path, path),
- );
- cursorSkillNames = new Set(
- skills
- .filter((skill) => skill.enabled && skill.userInvocable !== false)
- .map((skill) => skill.name),
- );
- }
- const userText = t3OrchestrationPromptForFirstRun({
- prompt: providerMessageTextWithAttachmentPaths({
- text:
- cursorSkillNames === undefined
- ? rawText
- : rewriteCursorSkillMentions(rawText, cursorSkillNames),
- attachments: turnInput.message.attachments,
- resolveAttachmentPath: (attachment) =>
- resolveAttachmentPath({ attachmentsDir: serverConfig.attachmentsDir, attachment }),
- }),
- runOrdinal: turnInput.runOrdinal,
- hasT3Mcp: cursorMcpServers(turnInput.threadId) !== undefined,
- });
- const images = yield* Effect.forEach(
- turnInput.message.attachments.filter(isProviderNativeImageAttachment),
- (attachment: ChatAttachment) =>
- Effect.gen(function* () {
- const path = resolveAttachmentPath({
- attachmentsDir: serverConfig.attachmentsDir,
- attachment,
- });
- if (path === null) {
- return yield* new ProviderAdapter.ProviderAdapterProtocolError({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- detail: `Invalid attachment id '${attachment.id}'.`,
- });
- }
- const bytes = yield* fileSystem.readFile(path).pipe(
- Effect.mapError(
- (cause) =>
- new ProviderAdapter.ProviderAdapterProtocolError({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- detail: `Failed to read attachment '${attachment.id}'.`,
- payload: cause,
- }),
- ),
- );
- return {
- data: Buffer.from(bytes).toString("base64"),
- mimeType: attachment.mimeType,
- };
- }),
- { concurrency: 1 },
- );
- if (userText.length === 0 && images.length === 0) {
- return yield* new ProviderAdapter.ProviderAdapterProtocolError({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- detail: "Cursor turn requires non-empty text or attachments.",
- });
- }
- const text = `${userText}\n\n${buildRuntimeInstructions({ harness: "Cursor", model: turnInput.modelSelection.model })}`;
- return images.length === 0
- ? text
- : ({
- text,
- images,
- } satisfies SDKUserMessage);
- });
-
- const startTurn = Effect.fn("CursorAdapterV2.startTurn")(
- function* (turnInput: ProviderAdapter.ProviderAdapterV2TurnInput) {
- const current = yield* Ref.get(activeTurn);
- if (current !== null) {
- return yield* new ProviderAdapter.ProviderAdapterProtocolError({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- detail: `Cursor provider turn ${current.providerTurnId} is still active.`,
- });
- }
- const agentId = nativeThreadId(turnInput.providerThread);
- const agent = yield* openAgent({
- operation: "resume",
- agentId,
- threadId: turnInput.threadId,
- modelSelection: turnInput.modelSelection,
- runtimePolicy: turnInput.runtimePolicy,
- });
- const message = yield* resolveUserMessage(turnInput);
- const mcpServers = cursorMcpServers(turnInput.threadId);
- const pendingUpdates: Array = [];
- let context: ActiveCursorTurn | null = null;
- const sdkRun = yield* agent.session.send({
- message,
- options: {
- model: cursorSdkModelSelection(turnInput.modelSelection),
- mode: turnInput.runtimePolicy.interactionMode === "plan" ? "plan" : "agent",
- ...(mcpServers === undefined ? {} : { mcpServers }),
- },
- onDelta: (update) => {
- if (context === null) {
- return Effect.sync(() => {
- pendingUpdates.push(update);
- });
- }
- return handleInteractionUpdate(context, update);
- },
- });
- const startedAt = yield* DateTime.now;
- const completed = yield* Deferred.make();
- const providerTurnId = idAllocator.derive.providerTurn({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeTurnId: sdkRun.runId,
- });
- context = {
- input: turnInput,
- run: sdkRun,
- providerTurnId,
- startedAt,
- completed,
- itemOrdinals: new Map(),
- tools: new Map(),
- subagents: new Map(),
- assistant: {
- current: null,
- nextSegment: 0,
- },
- assistantReply: new CursorTransportFailure(),
- reasoning: {
- current: null,
- nextSegment: 0,
- },
- interrupted: false,
- finalized: false,
- };
- yield* Ref.set(activeTurn, context);
- yield* emitProviderEvent({
- type: "provider_turn.updated",
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- providerTurn: providerTurnPayload({
- context,
- status: "running",
- completedAt: null,
- }),
- });
- yield* emitProviderEvent({
- type: "provider_thread.updated",
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- providerThread: {
- ...turnInput.providerThread,
- providerSessionId: session.id,
- status: "active",
- updatedAt: startedAt,
- },
- });
- for (const update of pendingUpdates) {
- yield* handleInteractionUpdate(context, update);
- }
-
- yield* sdkRun.wait.pipe(
- Effect.flatMap((result) =>
- Effect.gen(function* () {
- if (
- context !== null &&
- context.assistant.nextSegment === 0 &&
- result.result !== undefined &&
- result.result.length > 0
- ) {
- context.assistantReply.push(result.result);
- yield* appendTextSegment({
- context,
- stream: context.assistant,
- kind: "assistant",
- text: result.result,
- });
- }
- if (context !== null) {
- const transportFailure = context.assistantReply.failure;
- const status =
- transportFailure === undefined ? terminalStatus(context, result) : "failed";
- yield* finalizeTurn({
- context,
- status,
- ...(status === "failed"
- ? {
- failure: makeProviderFailure({
- cause:
- transportFailure ?? (result as { readonly error?: unknown }).error,
- class:
- transportFailure === undefined
- ? "provider_error"
- : "transport_error",
- }),
- }
- : {}),
- });
- }
- }),
- ),
- Effect.catch((cause) =>
- Effect.gen(function* () {
- if (context !== null) {
- yield* finalizeTurn({
- context,
- status: context.interrupted ? "interrupted" : "failed",
- ...(context.interrupted
- ? {}
- : {
- failure: makeProviderFailure({ cause, class: "transport_error" }),
- }),
- });
- }
- yield* Effect.logWarning("orchestration-v2.cursor-run-failed", {
- providerSessionId: input.providerSessionId,
- providerThreadId: turnInput.providerThread.id,
- providerTurnId,
- cause,
- });
- }),
- ),
- Effect.forkIn(sessionScope),
- );
- },
- (effect, turnInput) =>
- effect.pipe(
- Effect.mapError(
- (cause) =>
- new ProviderAdapter.ProviderAdapterTurnStartError({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- threadId: turnInput.threadId,
- providerThreadId: turnInput.providerThread.id,
- runId: turnInput.runId,
- cause,
- }),
- ),
- ),
- );
-
- const closeSession = Effect.fnUntraced(function* () {
- const existing = yield* Ref.get(liveAgent);
- if (existing !== null) {
- yield* existing.session.close.pipe(Effect.ignore);
- yield* Ref.set(liveAgent, null);
- }
- yield* runner.assertComplete.pipe(
- Effect.catchCause((cause) =>
- Effect.logWarning("orchestration-v2.cursor-runner-incomplete", {
- providerSessionId: input.providerSessionId,
- cause,
- }),
- ),
- );
- });
- yield* Effect.addFinalizer(() => closeSession());
-
- const runtime: ProviderAdapter.ProviderAdapterV2SessionRuntime = {
- instanceId: adapterOptions.instanceId,
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- providerSessionId: input.providerSessionId,
- providerSession: session,
- events: Stream.fromEffectRepeat(Queue.take(events)),
- ensureThread: Effect.fn("CursorAdapterV2.ensureThread")(
- function* (threadInput: ProviderAdapter.ProviderAdapterV2EnsureThreadInput) {
- const opened = yield* openAgent({
- operation: "create",
- threadId: threadInput.threadId,
- modelSelection: threadInput.modelSelection,
- runtimePolicy: threadInput.runtimePolicy,
- });
- const now = yield* DateTime.now;
- return makeProviderThread({
- idAllocator,
- providerInstanceId: adapterOptions.instanceId,
- appThreadId: threadInput.threadId,
- providerSessionId: input.providerSessionId,
- nativeThreadId: opened.nativeThreadId,
- now,
- });
- },
- (effect, threadInput) =>
- effect.pipe(
- Effect.mapError(
- (cause) =>
- new ProviderAdapter.ProviderAdapterEnsureThreadError({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- threadId: threadInput.threadId,
- cause,
- }),
- ),
- ),
- ),
- resumeThread: Effect.fn("CursorAdapterV2.resumeThread")(
- function* (threadInput: { readonly providerThread: OrchestrationV2ProviderThread }) {
- const agentId = nativeThreadId(threadInput.providerThread);
- yield* openAgent({
- operation: "resume",
- agentId,
- threadId: threadInput.providerThread.appThreadId ?? input.threadId,
- modelSelection: input.modelSelection,
- runtimePolicy: input.runtimePolicy,
- });
- const now = yield* DateTime.now;
- return {
- ...threadInput.providerThread,
- providerSessionId: input.providerSessionId,
- status: "idle" as const,
- updatedAt: now,
- };
- },
- (effect, threadInput) =>
- effect.pipe(
- Effect.mapError(
- (cause) =>
- new ProviderAdapter.ProviderAdapterResumeThreadError({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- providerSessionId: input.providerSessionId,
- providerThreadId: threadInput.providerThread.id,
- cause,
- }),
- ),
- ),
- ),
- startTurn,
- compactThread: (turnInput) =>
- startTurn({
- ...turnInput,
- message: { ...turnInput.message, text: "/compress" },
- }),
- steerTurn: (turnInput) =>
- Effect.fail(
- new ProviderAdapter.ProviderAdapterSteerRunUnsupportedError({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- providerThreadId: turnInput.providerThread.id,
- }),
- ),
- interruptTurn: Effect.fn("CursorAdapterV2.interruptTurn")(
- function* (turnInput: ProviderAdapter.ProviderAdapterV2InterruptInput) {
- const context = yield* Ref.get(activeTurn);
- // Stop on a settled turn: finalization already ended its tools
- // and subagents, so nothing of it is left running to stop.
- if (context === null && turnInput.requestRuntimeRestart === true) return;
- if (context?.providerTurnId !== turnInput.providerTurnId) {
- return yield* new ProviderAdapter.ProviderAdapterProtocolError({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- detail: `Cursor provider turn ${turnInput.providerTurnId} is not active.`,
- });
- }
- context.interrupted = true;
- yield* context.run.cancel;
- const stopped = yield* Deferred.await(context.completed).pipe(
- Effect.timeoutOption("10 seconds"),
- );
- if (Option.isSome(stopped)) {
- return;
- }
- yield* Effect.logWarning("orchestration-v2.cursor-interrupt-timeout", {
- providerSessionId: input.providerSessionId,
- providerThreadId: turnInput.providerThread.id,
- providerTurnId: turnInput.providerTurnId,
- });
- yield* finalizeTurn({ context, status: "interrupted" });
- },
- (effect, turnInput) =>
- effect.pipe(
- Effect.mapError(
- (cause) =>
- new ProviderAdapter.ProviderAdapterInterruptError({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- providerThreadId: turnInput.providerThread.id,
- providerTurnId: turnInput.providerTurnId,
- cause,
- }),
- ),
- ),
- ),
- respondToRuntimeRequest: (requestInput) =>
- Effect.fail(
- new ProviderAdapter.ProviderAdapterRuntimeRequestResponseError({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- requestId: requestInput.requestId,
- cause: new ProviderAdapter.ProviderAdapterProtocolError({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- detail: "Cursor Agent SDK does not expose interactive approval requests.",
- }),
- }),
- ),
- readThreadSnapshot: Effect.fn("CursorAdapterV2.readThreadSnapshot")(
- function* (snapshotInput) {
- const requestedAgentId = nativeThreadId(snapshotInput.providerThread);
- const agent = yield* openAgent({
- operation: "resume",
- agentId: requestedAgentId,
- threadId: snapshotInput.providerThread.appThreadId ?? input.threadId,
- modelSelection: input.modelSelection,
- runtimePolicy: input.runtimePolicy,
- });
- const messages = yield* agent.session.listMessages;
- const now = yield* DateTime.now;
- const threadId = snapshotInput.providerThread.appThreadId ?? input.threadId;
- const projectedMessages: Array = messages.flatMap(
- (message) => {
- const text = textFromAgentMessage(message);
- if (text.length === 0) {
- return [];
- }
- return [
- {
- createdBy: message.type === "user" ? "user" : "agent",
- creationSource: "provider",
- id: idAllocator.derive.messageFromProviderItem({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- nativeItemId: message.uuid,
- }),
- threadId,
- runId: null,
- nodeId: null,
- role: message.type,
- text,
- attachments: [],
- streaming: false,
- createdAt: now,
- updatedAt: now,
- },
- ];
- },
- );
- return {
- providerThread: {
- ...snapshotInput.providerThread,
- providerSessionId: input.providerSessionId,
- status: "idle" as const,
- updatedAt: now,
- },
- providerTurns: [],
- messages: projectedMessages,
- runtimeRequests: [],
- providerPayload: {
- agentId: requestedAgentId,
- messages,
- },
- };
- },
- (effect, snapshotInput) =>
- effect.pipe(
- Effect.mapError(
- (cause) =>
- new ProviderAdapter.ProviderAdapterReadThreadSnapshotError({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- providerThreadId: snapshotInput.providerThread.id,
- cause,
- }),
- ),
- ),
- ),
- rollbackThread: (rollbackInput) =>
- Effect.fail(
- new ProviderAdapter.ProviderAdapterRollbackThreadError({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- providerThreadId: rollbackInput.providerThread.id,
- checkpointId: rollbackInput.target.checkpointId,
- cause: "Cursor Agent SDK does not expose conversation rollback.",
- }),
- ),
- forkThread: (forkInput) =>
- Effect.fail(
- new ProviderAdapter.ProviderAdapterForkThreadError({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- providerThreadId: forkInput.sourceProviderThread.id,
- cause: "Cursor Agent SDK does not expose native agent forks.",
- }),
- ),
- };
- return runtime;
- },
- (effect, input) =>
- effect.pipe(
- Effect.mapError(
- (cause) =>
- new ProviderAdapter.ProviderAdapterOpenSessionError({
- driver: CursorAgentSdk.CURSOR_PROVIDER,
- providerSessionId: input.providerSessionId,
- cause,
- }),
- ),
- ),
- ),
- });
-}
-
-export type CursorAdapterV2DriverEnv =
- | CursorAgentSdk.CursorAgentSdkRunner
- | FileSystem.FileSystem
- | Path.Path
- | IdAllocator.IdAllocatorV2
- | ServerConfig.ServerConfig;
-
-export const CursorAdapterV2Driver: ProviderAdapterDriver<
- CursorSettings,
- CursorAdapterV2DriverEnv
-> = {
- driverKind: CURSOR_DRIVER_KIND,
- configSchema: CursorSettings,
- defaultConfig: (): CursorSettings => DEFAULT_CURSOR_SETTINGS,
- create: Effect.fn("CursorAdapterV2Driver.create")(
- function* (input: ProviderAdapterDriverCreateInput) {
- const hostEnvironment = yield* HostProcessEnvironment;
- const fileSystem = yield* FileSystem.FileSystem;
- const path = yield* Path.Path;
- const idAllocator = yield* IdAllocator.IdAllocatorV2;
- const runner = yield* CursorAgentSdk.CursorAgentSdkRunner;
- const serverConfig = yield* ServerConfig.ServerConfig;
- return makeCursorAdapterV2({
- instanceId: input.instanceId,
- settings: {
- ...input.config,
- enabled: input.enabled,
- },
- environment: mergeProviderInstanceEnvironment(input.environment, hostEnvironment),
- fileSystem,
- path,
- idAllocator,
- runner,
- serverConfig,
- });
- },
- (effect, input) =>
- effect.pipe(
- Effect.mapError(
- (cause) =>
- new ProviderAdapterDriverCreateError({
- driver: CURSOR_DRIVER_KIND,
- instanceId: input.instanceId,
- detail: "Failed to create Cursor Agent SDK adapter.",
- cause,
- }),
- ),
- ),
- ),
-};
-
-const layer: Layer.Layer<
- ProviderAdapter.ProviderAdapterV2,
- never,
- | CursorAgentSdk.CursorAgentSdkRunner
- | FileSystem.FileSystem
- | Path.Path
- | IdAllocator.IdAllocatorV2
- | ServerConfig.ServerConfig
-> = Layer.effect(
- ProviderAdapter.ProviderAdapterV2,
- Effect.gen(function* () {
- const hostEnvironment = yield* HostProcessEnvironment;
- const fileSystem = yield* FileSystem.FileSystem;
- const path = yield* Path.Path;
- const idAllocator = yield* IdAllocator.IdAllocatorV2;
- const runner = yield* CursorAgentSdk.CursorAgentSdkRunner;
- const serverConfig = yield* ServerConfig.ServerConfig;
- return makeCursorAdapterV2({
- instanceId: CURSOR_DEFAULT_INSTANCE_ID,
- settings: DEFAULT_CURSOR_SETTINGS,
- environment: hostEnvironment,
- fileSystem,
- path,
- idAllocator,
- runner,
- serverConfig,
- });
- }),
-);
diff --git a/apps/server/src/orchestration-v2/Adapters/CursorAgentSdk.test.ts b/apps/server/src/orchestration-v2/Adapters/CursorAgentSdk.test.ts
deleted file mode 100644
index 5754ec28706ce09a95e978333b7b4aca8197233b..0000000000000000000000000000000000000000
--- a/apps/server/src/orchestration-v2/Adapters/CursorAgentSdk.test.ts
+++ /dev/null
@@ -1,304 +0,0 @@
-import * as NodeServices from "@effect/platform-node/NodeServices";
-import { assert, describe, it } from "@effect/vitest";
-import { ProviderSessionId, ThreadId } from "@t3tools/contracts";
-import * as Effect from "effect/Effect";
-import * as Layer from "effect/Layer";
-import { vi } from "vite-plus/test";
-
-import * as ProviderEventLoggers from "../../provider/ProviderEventLoggers.ts";
-import * as CursorAgentSdk from "./CursorAgentSdk.ts";
-
-const cursorSdkMock = vi.hoisted(() => {
- const closeExecutionOrder: Array = [];
- const runOperations: Array = [];
- let agentCloseFailure: unknown;
- let staleRunActive = false;
- const runWait = vi.fn(async () => ({
- id: "run-cursor-agent-sdk-test",
- requestId: "request-cursor-agent-sdk-test",
- status: "finished",
- result: "ok",
- model: { id: "default" },
- durationMs: 1,
- }));
- const runCancel = vi.fn(async () => {});
- const agentClose = vi.fn(() => {
- closeExecutionOrder.push("sdk:agent.close");
- if (agentCloseFailure !== undefined) {
- throw agentCloseFailure;
- }
- });
- const send = vi.fn(
- async (
- _message: unknown,
- options: { readonly onDelta?: (input: { readonly update: unknown }) => Promise },
- ) => {
- runOperations.push("run.start");
- if (staleRunActive) {
- throw new Error("Agent agent-cursor-agent-sdk-test already has active run");
- }
- await options.onDelta?.({
- update: {
- type: "assistant-message-chunk",
- text: "hello",
- },
- });
- return {
- id: "run-cursor-agent-sdk-test",
- agentId: "agent-cursor-agent-sdk-test",
- wait: runWait,
- cancel: runCancel,
- };
- },
- );
- const create = vi.fn(async () => ({
- agentId: "agent-cursor-agent-sdk-test",
- send,
- close: agentClose,
- }));
- const resume = vi.fn(async () => ({
- agentId: "agent-cursor-agent-sdk-test",
- send,
- close: agentClose,
- }));
- const listRuns = vi.fn(async () => {
- runOperations.push("agent.listRuns");
- return {
- items: staleRunActive ? [{ id: "run-stale", status: "running" }] : [],
- };
- });
- const cancelRun = vi.fn(async () => {
- runOperations.push("agent.cancelRun");
- staleRunActive = false;
- });
-
- return {
- agentClose,
- cancelRun,
- closeExecutionOrder,
- create,
- listRuns,
- resume,
- runOperations,
- runCancel,
- runWait,
- send,
- setAgentCloseFailure: (failure: unknown) => {
- agentCloseFailure = failure;
- },
- setStaleRunActive: (active: boolean) => {
- staleRunActive = active;
- },
- };
-});
-
-vi.mock("../../provider/cursorSdk.ts", () => ({
- Agent: {
- create: cursorSdkMock.create,
- resume: cursorSdkMock.resume,
- listRuns: cursorSdkMock.listRuns,
- cancelRun: cursorSdkMock.cancelRun,
- messages: {
- list: vi.fn(async () => []),
- },
- },
-}));
-
-const layerTest = CursorAgentSdk.layer.pipe(
- Layer.provide(
- Layer.succeed(ProviderEventLoggers.ProviderEventLoggers, {
- native: {
- filePath: "cursor-agent-sdk-test.log",
- write: (event: unknown) =>
- Effect.sync(() => {
- if (
- typeof event === "object" &&
- event !== null &&
- Reflect.get(Reflect.get(event, "event"), "payload")?.type === "agent.close"
- ) {
- cursorSdkMock.closeExecutionOrder.push("protocol:agent.close");
- }
- }),
- close: () => Effect.void,
- },
- canonical: undefined,
- }),
- ),
- Layer.provide(NodeServices.layer),
-);
-
-describe("CursorAgentSdkRunner", () => {
- it.effect("leaves a resumed agent alone when its next send succeeds", () =>
- Effect.gen(function* () {
- cursorSdkMock.runOperations.length = 0;
- cursorSdkMock.setStaleRunActive(false);
-
- const runner = yield* CursorAgentSdk.CursorAgentSdkRunner;
- const session = yield* runner.open({
- operation: "resume",
- agentId: "agent-cursor-agent-sdk-test",
- options: { model: { id: "default" }, mode: "agent", local: { cwd: process.cwd() } },
- threadId: ThreadId.make("thread-cursor-agent-sdk-normal-resume-test"),
- providerSessionId: ProviderSessionId.make(
- "provider-session-cursor-agent-sdk-normal-resume-test",
- ),
- });
- const run = yield* session.send({ message: "continue" });
- yield* run.wait;
-
- assert.deepStrictEqual(cursorSdkMock.runOperations, ["run.start"]);
- }).pipe(Effect.provide(layerTest)),
- );
-
- it.effect("retries a resumed send after cancelling its abandoned local run", () =>
- Effect.gen(function* () {
- cursorSdkMock.runOperations.length = 0;
- cursorSdkMock.listRuns.mockClear();
- cursorSdkMock.cancelRun.mockClear();
- cursorSdkMock.setStaleRunActive(true);
-
- const runner = yield* CursorAgentSdk.CursorAgentSdkRunner;
- const session = yield* runner.open({
- operation: "resume",
- agentId: "agent-cursor-agent-sdk-test",
- options: { model: { id: "default" }, mode: "agent", local: { cwd: process.cwd() } },
- threadId: ThreadId.make("thread-cursor-agent-sdk-stale-run-test"),
- providerSessionId: ProviderSessionId.make(
- "provider-session-cursor-agent-sdk-stale-run-test",
- ),
- });
- assert.deepStrictEqual(cursorSdkMock.runOperations, []);
- const run = yield* session.send({ message: "continue" });
- yield* run.wait;
-
- assert.deepStrictEqual(cursorSdkMock.runOperations, [
- "run.start",
- "agent.listRuns",
- "agent.cancelRun",
- "run.start",
- ]);
- assert.deepStrictEqual(cursorSdkMock.listRuns.mock.calls[0], [
- "agent-cursor-agent-sdk-test",
- { runtime: "local", cwd: process.cwd(), limit: 1 },
- ]);
- assert.deepStrictEqual(cursorSdkMock.cancelRun.mock.calls[0], [
- "run-stale",
- {
- runtime: "local",
- cwd: process.cwd(),
- },
- ]);
- }).pipe(Effect.provide(layerTest)),
- );
-
- it.effect("logs agent.close before invoking the Cursor SDK", () =>
- Effect.gen(function* () {
- cursorSdkMock.agentClose.mockClear();
- cursorSdkMock.closeExecutionOrder.length = 0;
- cursorSdkMock.setAgentCloseFailure(undefined);
-
- const runner = yield* CursorAgentSdk.CursorAgentSdkRunner;
- const session = yield* runner.open({
- operation: "create",
- options: {
- model: { id: "default" },
- mode: "agent",
- local: {
- cwd: process.cwd(),
- },
- },
- threadId: ThreadId.make("thread-cursor-agent-sdk-close-order-test"),
- providerSessionId: ProviderSessionId.make(
- "provider-session-cursor-agent-sdk-close-order-test",
- ),
- });
-
- cursorSdkMock.closeExecutionOrder.length = 0;
- yield* session.close;
-
- assert.deepStrictEqual(cursorSdkMock.closeExecutionOrder, [
- "protocol:agent.close",
- "sdk:agent.close",
- ]);
- assert.equal(cursorSdkMock.agentClose.mock.calls.length, 1);
- }).pipe(Effect.provide(layerTest)),
- );
-
- it.effect("logs agent.close even when the Cursor SDK close fails", () =>
- Effect.gen(function* () {
- cursorSdkMock.agentClose.mockClear();
- cursorSdkMock.closeExecutionOrder.length = 0;
- const closeFailure = new Error("agent close failed");
- cursorSdkMock.setAgentCloseFailure(closeFailure);
-
- const runner = yield* CursorAgentSdk.CursorAgentSdkRunner;
- const session = yield* runner.open({
- operation: "create",
- options: {
- model: { id: "default" },
- mode: "agent",
- local: {
- cwd: process.cwd(),
- },
- },
- threadId: ThreadId.make("thread-cursor-agent-sdk-close-failure-test"),
- providerSessionId: ProviderSessionId.make(
- "provider-session-cursor-agent-sdk-close-failure-test",
- ),
- });
-
- cursorSdkMock.closeExecutionOrder.length = 0;
- const error = yield* Effect.flip(session.close);
-
- assert.instanceOf(error, CursorAgentSdk.CursorAgentSdkRunnerError);
- assert.equal(error.method, "agent.close");
- assert.strictEqual(error.cause, closeFailure);
- assert.deepStrictEqual(cursorSdkMock.closeExecutionOrder, [
- "protocol:agent.close",
- "sdk:agent.close",
- ]);
- assert.equal(cursorSdkMock.agentClose.mock.calls.length, 1);
- cursorSdkMock.setAgentCloseFailure(undefined);
- }).pipe(Effect.provide(layerTest)),
- );
-
- it.effect("surfaces interaction callback failures through run.wait", () =>
- Effect.gen(function* () {
- cursorSdkMock.create.mockClear();
- cursorSdkMock.runWait.mockClear();
- cursorSdkMock.send.mockClear();
-
- const runner = yield* CursorAgentSdk.CursorAgentSdkRunner;
- const session = yield* runner.open({
- operation: "create",
- options: {
- model: { id: "default" },
- mode: "agent",
- local: {
- cwd: process.cwd(),
- autoReview: false,
- sandboxOptions: { enabled: false },
- enableAgentRetries: true,
- },
- },
- threadId: ThreadId.make("thread-cursor-agent-sdk-test"),
- providerSessionId: ProviderSessionId.make("provider-session-cursor-agent-sdk-test"),
- });
-
- const callbackFailure = new Error("delta callback failed");
- const run = yield* session.send({
- message: "hello",
- onDelta: () => Effect.fail(callbackFailure),
- });
-
- const error = yield* Effect.flip(run.wait);
-
- assert.instanceOf(error, CursorAgentSdk.CursorAgentSdkRunnerError);
- assert.equal(error.method, "run.wait");
- assert.strictEqual(error.cause, callbackFailure);
- assert.equal(cursorSdkMock.create.mock.calls.length, 1);
- assert.equal(cursorSdkMock.send.mock.calls.length, 1);
- assert.equal(cursorSdkMock.runWait.mock.calls.length, 1);
- }).pipe(Effect.provide(layerTest)),
- );
-});
diff --git a/apps/server/src/orchestration-v2/Adapters/CursorAgentSdk.ts b/apps/server/src/orchestration-v2/Adapters/CursorAgentSdk.ts
deleted file mode 100644
index 036a93a0e557f963666f2d375cd400ddc257937c..0000000000000000000000000000000000000000
--- a/apps/server/src/orchestration-v2/Adapters/CursorAgentSdk.ts
+++ /dev/null
@@ -1,612 +0,0 @@
-import type {
- AgentMessage,
- AgentOptions,
- InteractionUpdate,
- RunResult,
- SDKUserMessage,
- SendOptions,
-} from "@cursor/sdk";
-import {
- type OrchestrationV2ProviderSession,
- ProviderDriverKind,
- type ThreadId,
-} from "@t3tools/contracts";
-import * as Context from "effect/Context";
-import * as Effect from "effect/Effect";
-import * as Layer from "effect/Layer";
-import * as Schema from "effect/Schema";
-
-import { Agent, createAgentPlatform } from "../../provider/cursorSdk.ts";
-import type { EventNdjsonLogger } from "../../provider/EventNdjsonLogger.ts";
-import * as ProviderEventLoggers from "../../provider/ProviderEventLoggers.ts";
-
-export const CURSOR_AGENT_SDK_PROTOCOL = "cursor-agent-sdk.local" as const;
-export const CURSOR_PROVIDER = ProviderDriverKind.make("cursor");
-
-export class CursorAgentSdkRunnerError extends Schema.TaggedError()(
- "CursorAgentSdkRunnerError",
- {
- method: Schema.String,
- cause: Schema.Defect(),
- },
-) {
- override get message(): string {
- return `Cursor Agent SDK ${this.method} failed.`;
- }
-}
-
-const isCursorAgentSdkRunnerError = Schema.is(CursorAgentSdkRunnerError);
-
-export interface CursorAgentSdkOpenInput {
- readonly operation: "create" | "resume";
- readonly agentId?: string;
- readonly options: AgentOptions;
- readonly threadId: ThreadId;
- readonly providerSessionId: OrchestrationV2ProviderSession["id"];
-}
-
-export interface CursorAgentSdkSendInput {
- readonly message: string | SDKUserMessage;
- readonly options?: Omit;
- readonly onDelta?: (update: InteractionUpdate) => Effect.Effect;
-}
-
-export interface CursorAgentSdkRun {
- readonly runId: string;
- readonly agentId: string;
- readonly wait: Effect.Effect;
- readonly cancel: Effect.Effect;
-}
-
-export interface CursorAgentSdkSession {
- readonly agentId: string;
- readonly send: (
- input: CursorAgentSdkSendInput,
- ) => Effect.Effect;
- readonly listMessages: Effect.Effect, CursorAgentSdkRunnerError>;
- readonly close: Effect.Effect;
-}
-
-export interface CursorAgentSdkRunnerShape {
- readonly open: (
- input: CursorAgentSdkOpenInput,
- ) => Effect.Effect;
- readonly assertComplete: Effect.Effect;
-}
-
-export class CursorAgentSdkRunner extends Context.Service<
- CursorAgentSdkRunner,
- CursorAgentSdkRunnerShape
->()("t3/orchestration-v2/Adapters/CursorAgentSdk/CursorAgentSdkRunner") {}
-
-export interface CursorAgentSdkLoggedAgentOptions {
- readonly model?: AgentOptions["model"];
- readonly hasName?: boolean;
- readonly mode?: AgentOptions["mode"];
- readonly local?: {
- readonly hasCwd?: boolean;
- readonly autoReview?: boolean;
- readonly settingSources?: AgentOptions["local"] extends infer Local
- ? Local extends { readonly settingSources?: infer Sources }
- ? Sources
- : never
- : never;
- readonly sandboxEnabled?: boolean;
- readonly enableAgentRetries?: boolean;
- readonly hasCustomTools?: boolean;
- };
- readonly agents?: ReadonlyArray;
-}
-
-export interface CursorAgentSdkLoggedSendOptions {
- readonly model?: SendOptions["model"];
- readonly mode?: SendOptions["mode"];
- readonly local?: {
- readonly force?: boolean;
- readonly hasCustomTools?: boolean;
- };
- readonly idempotencyKey?: string;
-}
-
-export type CursorAgentSdkProtocolLogEvent =
- | {
- readonly direction: "outgoing";
- readonly stage: "decoded";
- readonly payload: {
- readonly type: "agent.open";
- readonly operation: CursorAgentSdkOpenInput["operation"];
- readonly agentId?: string;
- readonly options: CursorAgentSdkLoggedAgentOptions;
- };
- }
- | {
- readonly direction: "incoming";
- readonly stage: "decoded";
- readonly payload: {
- readonly type: "agent.opened";
- readonly agentId: string;
- };
- }
- | {
- readonly direction: "outgoing";
- readonly stage: "decoded";
- readonly payload: {
- readonly type: "run.start";
- readonly message: string | SDKUserMessage;
- readonly options: CursorAgentSdkLoggedSendOptions;
- };
- }
- | {
- readonly direction: "incoming";
- readonly stage: "decoded";
- readonly payload: {
- readonly type: "run.started";
- readonly runId: string;
- readonly agentId: string;
- };
- }
- | {
- readonly direction: "incoming";
- readonly stage: "decoded";
- readonly payload: {
- readonly type: "interaction.update";
- readonly runId: string;
- readonly update: InteractionUpdate;
- };
- }
- | {
- readonly direction: "incoming";
- readonly stage: "decoded";
- readonly payload: {
- readonly type: "run.completed";
- readonly result: RunResult;
- };
- }
- | {
- readonly direction: "outgoing";
- readonly stage: "decoded";
- readonly payload: {
- readonly type: "run.cancel";
- readonly runId: string;
- };
- }
- | {
- readonly direction: "outgoing";
- readonly stage: "decoded";
- readonly payload: {
- readonly type: "agent.messages.list";
- readonly agentId: string;
- };
- }
- | {
- readonly direction: "incoming";
- readonly stage: "decoded";
- readonly payload: {
- readonly type: "agent.messages";
- readonly agentId: string;
- readonly messages: ReadonlyArray;
- };
- }
- | {
- readonly direction: "outgoing";
- readonly stage: "decoded";
- readonly payload: {
- readonly type: "agent.close";
- readonly agentId: string;
- };
- };
-
-export type CursorAgentSdkProtocolLogger = (
- event: CursorAgentSdkProtocolLogEvent,
-) => Effect.Effect;
-
-function runnerError(cause: unknown, method: string): CursorAgentSdkRunnerError {
- return isCursorAgentSdkRunnerError(cause)
- ? cause
- : new CursorAgentSdkRunnerError({ method, cause });
-}
-
-function isActiveRunConflict(cause: unknown): boolean {
- return cause instanceof Error && /already has active run/i.test(cause.message);
-}
-
-export function isCursorCancellationError(cause: unknown): boolean {
- let current = cause;
- const seen = new Set