authorgravatar for maria@kuuro.netmaria <maria@kuuro.net> 2026-09-02 19:27:53-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-02 19:27:53-04:00
log994bd7373cf3a335c204a617604e690ed4c00cba
tree8f9bf41d33c6e09a0a8fd7caddf630ebcdc5db73
parentc742edd46c5b6792ec8647f934a4703f9103aa82
signature Signed by PGP key B5690EEEBB952194

fix(cursor): honor auto and full access modes (#9283)

Co-authored-by: maria-rcks <254055478+maria-rcks@users.noreply.github.com>

5 files changed, 59 insertions(+), 5 deletions(-)

apps/server/src/provider/Layers/CursorAdapter.test.ts+4-1
...@@ -820,6 +820,9 @@ cursorAdapterTestLayer("CursorAdapterLive", (it) => {...@@ -820,6 +820,9 @@ cursorAdapterTestLayer("CursorAdapterLive", (it) => {
820 );820 );
821 assert.isDefined(permissionResponse);821 assert.isDefined(permissionResponse);
822822
823 const argvRuns = yield* Effect.promise(() => readArgvLog(argvLogPath));
824 assert.deepStrictEqual(argvRuns, [["--force", "acp"]]);
825
823 yield* adapter.stopSession(threadId);826 yield* adapter.stopSession(threadId);
824 }),827 }),
825 );828 );
...@@ -1260,7 +1263,7 @@ cursorAdapterTestLayer("CursorAdapterLive", (it) => {...@@ -1260,7 +1263,7 @@ cursorAdapterTestLayer("CursorAdapterLive", (it) => {
12601263
1261 const argvRuns = yield* Effect.promise(() => readArgvLog(argvLogPath));1264 const argvRuns = yield* Effect.promise(() => readArgvLog(argvLogPath));
1262 assert.lengthOf(argvRuns, 1, "session should not restart — only one spawn");1265 assert.lengthOf(argvRuns, 1, "session should not restart — only one spawn");
1263 assert.deepStrictEqual(argvRuns[0], ["acp"]);1266 assert.deepStrictEqual(argvRuns[0], ["--force", "acp"]);
12641267
1265 const requests = yield* Effect.promise(() => readJsonLines(requestLogPath));1268 const requests = yield* Effect.promise(() => readJsonLines(requestLogPath));
1266 const setConfigRequests = requests.filter(1269 const setConfigRequests = requests.filter(
apps/server/src/provider/Layers/CursorAdapter.ts+1
...@@ -537,6 +537,7 @@ export function makeCursorAdapter(...@@ -537,6 +537,7 @@ export function makeCursorAdapter(
537 ...(options?.environment ? { environment: options.environment } : {}),537 ...(options?.environment ? { environment: options.environment } : {}),
538 childProcessSpawner,538 childProcessSpawner,
539 cwd,539 cwd,
540 runtimeMode: input.runtimeMode,
540 ...(resumeSessionId ? { resumeSessionId } : {}),541 ...(resumeSessionId ? { resumeSessionId } : {}),
541 clientInfo: { name: "t3-code", version: "0.0.0" },542 clientInfo: { name: "t3-code", version: "0.0.0" },
542 ...(mcpSession543 ...(mcpSession
apps/server/src/provider/acp/CursorAcpSupport.test.ts+27
...@@ -74,6 +74,33 @@ describe("buildCursorAcpSpawnInput", () => {...@@ -74,6 +74,33 @@ describe("buildCursorAcpSpawnInput", () => {
74 cwd: "/tmp/project",74 cwd: "/tmp/project",
75 });75 });
76 });76 });
77
78 it("forces approval in full-access mode", () => {
79 expect(buildCursorAcpSpawnInput(undefined, "/tmp/project", undefined, "full-access")).toEqual({
80 command: "cursor-agent",
81 args: ["--force", "acp"],
82 cwd: "/tmp/project",
83 });
84 });
85
86 it("uses Cursor auto-review in auto mode", () => {
87 expect(buildCursorAcpSpawnInput(undefined, "/tmp/project", undefined, "auto")).toEqual({
88 command: "cursor-agent",
89 args: ["--auto-review", "acp"],
90 cwd: "/tmp/project",
91 });
92 });
93
94 it.each(["approval-required", "auto-accept-edits"] as const)(
95 "does not relax approval in %s mode",
96 (runtimeMode) => {
97 expect(buildCursorAcpSpawnInput(undefined, "/tmp/project", undefined, runtimeMode)).toEqual({
98 command: "cursor-agent",
99 args: ["acp"],
100 cwd: "/tmp/project",
101 });
102 },
103 );
77});104});
78105
79describe("applyCursorAcpModelSelection", () => {106describe("applyCursorAcpModelSelection", () => {
apps/server/src/provider/acp/CursorAcpSupport.ts+25-2
...@@ -1,4 +1,8 @@...@@ -1,4 +1,8 @@
1import { type CursorSettings, type ProviderOptionSelection } from "@t3tools/contracts";1import {
2 type CursorSettings,
3 type ProviderOptionSelection,
4 type RuntimeMode,
5} from "@t3tools/contracts";
2import * as Crypto from "effect/Crypto";6import * as Crypto from "effect/Crypto";
3import * as Effect from "effect/Effect";7import * as Effect from "effect/Effect";
4import * as Layer from "effect/Layer";8import * as Layer from "effect/Layer";
...@@ -15,6 +19,17 @@ import * as AcpSessionRuntime from "./AcpSessionRuntime.ts";...@@ -15,6 +19,17 @@ import * as AcpSessionRuntime from "./AcpSessionRuntime.ts";
1519
16type CursorAcpRuntimeCursorSettings = Pick<CursorSettings, "apiEndpoint" | "binaryPath">;20type CursorAcpRuntimeCursorSettings = Pick<CursorSettings, "apiEndpoint" | "binaryPath">;
1721
22function cursorAcpPermissionArgs(runtimeMode?: RuntimeMode): ReadonlyArray<string> {
23 switch (runtimeMode) {
24 case "auto":
25 return ["--auto-review"];
26 case "full-access":
27 return ["--force"];
28 default:
29 return [];
30 }
31}
32
18export interface CursorAcpRuntimeInput extends Omit<33export interface CursorAcpRuntimeInput extends Omit<
19 AcpSessionRuntime.AcpSessionRuntimeOptions,34 AcpSessionRuntime.AcpSessionRuntimeOptions,
20 "authMethodId" | "clientCapabilities" | "spawn"35 "authMethodId" | "clientCapabilities" | "spawn"
...@@ -22,6 +37,7 @@ export interface CursorAcpRuntimeInput extends Omit<...@@ -22,6 +37,7 @@ export interface CursorAcpRuntimeInput extends Omit<
22 readonly childProcessSpawner: ChildProcessSpawner.ChildProcessSpawner["Service"];37 readonly childProcessSpawner: ChildProcessSpawner.ChildProcessSpawner["Service"];
23 readonly cursorSettings: CursorAcpRuntimeCursorSettings | null | undefined;38 readonly cursorSettings: CursorAcpRuntimeCursorSettings | null | undefined;
24 readonly environment?: NodeJS.ProcessEnv;39 readonly environment?: NodeJS.ProcessEnv;
40 readonly runtimeMode?: RuntimeMode;
25}41}
2642
27export interface CursorAcpModelSelectionErrorContext {43export interface CursorAcpModelSelectionErrorContext {
...@@ -34,11 +50,13 @@ export function buildCursorAcpSpawnInput(...@@ -34,11 +50,13 @@ export function buildCursorAcpSpawnInput(
34 cursorSettings: CursorAcpRuntimeCursorSettings | null | undefined,50 cursorSettings: CursorAcpRuntimeCursorSettings | null | undefined,
35 cwd: string,51 cwd: string,
36 environment?: NodeJS.ProcessEnv,52 environment?: NodeJS.ProcessEnv,
53 runtimeMode?: RuntimeMode,
37): AcpSessionRuntime.AcpSpawnInput {54): AcpSessionRuntime.AcpSpawnInput {
38 return {55 return {
39 command: cursorSettings?.binaryPath || "cursor-agent",56 command: cursorSettings?.binaryPath || "cursor-agent",
40 args: [57 args: [
41 ...(cursorSettings?.apiEndpoint ? (["-e", cursorSettings.apiEndpoint] as const) : []),58 ...(cursorSettings?.apiEndpoint ? (["-e", cursorSettings.apiEndpoint] as const) : []),
59 ...cursorAcpPermissionArgs(runtimeMode),
42 "acp",60 "acp",
43 ],61 ],
44 cwd,62 cwd,
...@@ -57,7 +75,12 @@ export const makeCursorAcpRuntime = (...@@ -57,7 +75,12 @@ export const makeCursorAcpRuntime = (
57 const acpContext = yield* Layer.build(75 const acpContext = yield* Layer.build(
58 AcpSessionRuntime.layer({76 AcpSessionRuntime.layer({
59 ...input,77 ...input,
60 spawn: buildCursorAcpSpawnInput(input.cursorSettings, input.cwd, input.environment),78 spawn: buildCursorAcpSpawnInput(
79 input.cursorSettings,
80 input.cwd,
81 input.environment,
82 input.runtimeMode,
83 ),
61 authMethodId: "cursor_login",84 authMethodId: "cursor_login",
62 clientCapabilities: CURSOR_PARAMETERIZED_MODEL_PICKER_CAPABILITIES,85 clientCapabilities: CURSOR_PARAMETERIZED_MODEL_PICKER_CAPABILITIES,
63 }).pipe(86 }).pipe(
docs/user/permission-modes.md+2-2
...@@ -17,8 +17,8 @@ without prompting; commands and anything else still stop for approval....@@ -17,8 +17,8 @@ without prompting; commands and anything else still stop for approval.
1717
18**Auto**: routine actions proceed without you; risky ones still ask. How this is enforced depends18**Auto**: routine actions proceed without you; risky ones still ask. How this is enforced depends
19on the provider: Codex delegates routine approvals to an AI reviewer, Claude uses its own auto19on the provider: Codex delegates routine approvals to an AI reviewer, Claude uses its own auto
20permission mode, and providers without an equivalent (such as OpenCode) fall back to asking, like20permission mode, Cursor uses Smart Auto review, and providers without an equivalent (such as
21Supervised.21OpenCode) fall back to asking, like Supervised.
2222
23**Full access**: allow commands and edits without prompts. The default. The agent runs23**Full access**: allow commands and edits without prompts. The default. The agent runs
24unattended until it finishes or asks a question of its own.24unattended until it finishes or asks a question of its own.