| author | |
| committer | |
| log | 994bd7373cf3a335c204a617604e690ed4c00cba |
| tree | 8f9bf41d33c6e09a0a8fd7caddf630ebcdc5db73 |
| parent | c742edd46c5b6792ec8647f934a4703f9103aa82 |
| signature | Signed by PGP key B5690EEEBB952194 |
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); |
| 822 | 822 | ||
| 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) => { |
| 1260 | 1263 | ||
| 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"]); |
| 1264 | 1267 | ||
| 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 | ...(mcpSession | 543 | ...(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 | }); |
| 78 | 105 | ||
| 79 | describe("applyCursorAcpModelSelection", () => { | 106 | describe("applyCursorAcpModelSelection", () => { |
apps/server/src/provider/acp/CursorAcpSupport.ts+25-2| ... | @@ -1,4 +1,8 @@ | ... | @@ -1,4 +1,8 @@ |
| 1 | import { type CursorSettings, type ProviderOptionSelection } from "@t3tools/contracts"; | 1 | import { |
| 2 | type CursorSettings, | ||
| 3 | type ProviderOptionSelection, | ||
| 4 | type RuntimeMode, | ||
| 5 | } from "@t3tools/contracts"; | ||
| 2 | import * as Crypto from "effect/Crypto"; | 6 | import * as Crypto from "effect/Crypto"; |
| 3 | import * as Effect from "effect/Effect"; | 7 | import * as Effect from "effect/Effect"; |
| 4 | import * as Layer from "effect/Layer"; | 8 | import * 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"; |
| 15 | 19 | ||
| 16 | type CursorAcpRuntimeCursorSettings = Pick<CursorSettings, "apiEndpoint" | "binaryPath">; | 20 | type CursorAcpRuntimeCursorSettings = Pick<CursorSettings, "apiEndpoint" | "binaryPath">; |
| 17 | 21 | ||
| 22 | function 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 | |||
| 18 | export interface CursorAcpRuntimeInput extends Omit< | 33 | export 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 | } |
| 26 | 42 | ||
| 27 | export interface CursorAcpModelSelectionErrorContext { | 43 | export 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. |
| 17 | 17 | ||
| 18 | **Auto**: routine actions proceed without you; risky ones still ask. How this is enforced depends | 18 | **Auto**: routine actions proceed without you; risky ones still ask. How this is enforced depends |
| 19 | on the provider: Codex delegates routine approvals to an AI reviewer, Claude uses its own auto | 19 | on the provider: Codex delegates routine approvals to an AI reviewer, Claude uses its own auto |
| 20 | permission mode, and providers without an equivalent (such as OpenCode) fall back to asking, like | 20 | permission mode, Cursor uses Smart Auto review, and providers without an equivalent (such as |
| 21 | Supervised. | 21 | OpenCode) fall back to asking, like Supervised. |
| 22 | 22 | ||
| 23 | **Full access**: allow commands and edits without prompts. The default. The agent runs | 23 | **Full access**: allow commands and edits without prompts. The default. The agent runs |
| 24 | unattended until it finishes or asks a question of its own. | 24 | unattended until it finishes or asks a question of its own. |