mirror of
https://github.com/cline/cline.git
synced 2026-08-30 17:20:20 +08:00
Add Auto Approval to ACP (#12897)
* Add Auto Approval to ACP * Update apps/cli/src/acp/auto-approve.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Update apps/cli/src/acp/auto-approve.test.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
This commit is contained in:
@@ -46,6 +46,11 @@ import {
|
||||
authenticateAcpProvider,
|
||||
isAcpAuthMethodId,
|
||||
} from "./auth";
|
||||
import {
|
||||
AUTO_APPROVE_CONFIG_ID,
|
||||
buildAutoApproveConfigOption,
|
||||
parseAutoApproveValue,
|
||||
} from "./auto-approve";
|
||||
import {
|
||||
buildOrganizationConfigOption,
|
||||
fetchClineOrganizations,
|
||||
@@ -75,6 +80,8 @@ interface SessionState {
|
||||
currentProviderId: string;
|
||||
/** Current model id for the session. */
|
||||
currentModelId: string;
|
||||
/** When true, all tool calls are approved without asking the client. */
|
||||
autoApproveTools: boolean;
|
||||
/** Active session manager for the running agent, if any. */
|
||||
sessionManager?: ClineCore;
|
||||
/** Internal session id within the session manager. */
|
||||
@@ -100,12 +107,17 @@ export class AcpAgent implements Agent {
|
||||
private sessions = new Map<string, SessionState>();
|
||||
private readonly conn: AgentSideConnection;
|
||||
private readonly providerSettingsManager = new ProviderSettingsManager();
|
||||
private readonly defaultAutoApproveTools: boolean;
|
||||
|
||||
/** Set after a successful `authenticate` call. */
|
||||
private authResult?: AcpAuthResult;
|
||||
|
||||
constructor(conn: AgentSideConnection) {
|
||||
constructor(
|
||||
conn: AgentSideConnection,
|
||||
options?: { autoApproveTools?: boolean },
|
||||
) {
|
||||
this.conn = conn;
|
||||
this.defaultAutoApproveTools = options?.autoApproveTools ?? false;
|
||||
}
|
||||
|
||||
async initialize(_params: InitializeRequest): Promise<InitializeResponse> {
|
||||
@@ -190,6 +202,7 @@ export class AcpAgent implements Agent {
|
||||
currentMode: defaultMode,
|
||||
currentProviderId: providerId,
|
||||
currentModelId: defaultModelId,
|
||||
autoApproveTools: this.defaultAutoApproveTools,
|
||||
});
|
||||
|
||||
const availableModels = Object.entries(providerModels).map(
|
||||
@@ -217,6 +230,7 @@ export class AcpAgent implements Agent {
|
||||
await buildProviderConfigOption(providerId),
|
||||
buildModelConfigOption(defaultModelId, providerModels),
|
||||
buildModeConfigOption(defaultMode),
|
||||
buildAutoApproveConfigOption(this.defaultAutoApproveTools),
|
||||
...(organizationOption ? [organizationOption] : []),
|
||||
],
|
||||
};
|
||||
@@ -254,6 +268,7 @@ export class AcpAgent implements Agent {
|
||||
process.env.CLINE_MODEL,
|
||||
providerModels,
|
||||
),
|
||||
autoApproveTools: this.defaultAutoApproveTools,
|
||||
};
|
||||
this.sessions.set(params.sessionId, session);
|
||||
}
|
||||
@@ -511,6 +526,18 @@ export class AcpAgent implements Agent {
|
||||
break;
|
||||
}
|
||||
|
||||
case AUTO_APPROVE_CONFIG_ID: {
|
||||
const autoApprove = parseAutoApproveValue(params.value);
|
||||
if (autoApprove === undefined) {
|
||||
throw RequestError.invalidParams(
|
||||
undefined,
|
||||
`Invalid auto-approve value: ${String(params.value)} (must be a boolean)`,
|
||||
);
|
||||
}
|
||||
session.autoApproveTools = autoApprove;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw RequestError.invalidParams(
|
||||
undefined,
|
||||
@@ -660,7 +687,9 @@ export class AcpAgent implements Agent {
|
||||
toolPolicies: config.toolPolicies,
|
||||
capabilities: {
|
||||
requestToolApproval: (request) =>
|
||||
requestAcpToolApproval(this.conn, acpSessionId, request),
|
||||
session.autoApproveTools
|
||||
? Promise.resolve({ approved: true })
|
||||
: requestAcpToolApproval(this.conn, acpSessionId, request),
|
||||
},
|
||||
cwd: config.cwd,
|
||||
workspaceRoot: config.workspaceRoot,
|
||||
@@ -884,6 +913,7 @@ async function buildAllConfigOptions(
|
||||
providerOption,
|
||||
buildModelConfigOption(session.currentModelId, providerModels),
|
||||
buildModeConfigOption(session.currentMode),
|
||||
buildAutoApproveConfigOption(session.autoApproveTools),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
AUTO_APPROVE_CONFIG_ID,
|
||||
buildAutoApproveConfigOption,
|
||||
parseAutoApproveValue,
|
||||
} from "./auto-approve";
|
||||
|
||||
describe("buildAutoApproveConfigOption", () => {
|
||||
it("builds a boolean config option reflecting the current value", () => {
|
||||
const option = buildAutoApproveConfigOption(true);
|
||||
|
||||
expect(option).toMatchObject({
|
||||
type: "boolean",
|
||||
id: AUTO_APPROVE_CONFIG_ID,
|
||||
currentValue: true,
|
||||
});
|
||||
expect(option.name).toBeTruthy();
|
||||
});
|
||||
|
||||
it("defaults to disabled when the session has it off", () => {
|
||||
const option = buildAutoApproveConfigOption(false);
|
||||
|
||||
expect(option).toMatchObject({ type: "boolean", currentValue: false });
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseAutoApproveValue", () => {
|
||||
it("accepts booleans", () => {
|
||||
expect(parseAutoApproveValue(true)).toBe(true);
|
||||
expect(parseAutoApproveValue(false)).toBe(false);
|
||||
});
|
||||
|
||||
it("accepts the string forms sent by older clients", () => {
|
||||
expect(parseAutoApproveValue("true")).toBe(true);
|
||||
expect(parseAutoApproveValue("false")).toBe(false);
|
||||
});
|
||||
|
||||
it("fails closed for unrecognized values", () => {
|
||||
expect(parseAutoApproveValue("yes")).toBeUndefined();
|
||||
expect(parseAutoApproveValue(1)).toBeUndefined();
|
||||
expect(parseAutoApproveValue(null)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns undefined when no value was provided", () => {
|
||||
expect(parseAutoApproveValue(undefined)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { SessionConfigOption } from "@agentclientprotocol/sdk";
|
||||
|
||||
export const AUTO_APPROVE_CONFIG_ID = "auto_approve";
|
||||
|
||||
export function buildAutoApproveConfigOption(
|
||||
currentValue: boolean,
|
||||
): SessionConfigOption {
|
||||
return {
|
||||
type: "boolean",
|
||||
id: AUTO_APPROVE_CONFIG_ID,
|
||||
name: "Auto-approve tools",
|
||||
description:
|
||||
"Automatically approve all tool calls without asking for permission",
|
||||
currentValue,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpret the value of a `session/set_config_option` request for the
|
||||
* auto-approve option.
|
||||
*
|
||||
* The ACP schema sends booleans for boolean options, but clients that predate
|
||||
* boolean options may send the string form, so both are accepted. Returns
|
||||
* `undefined` for anything else so the caller can reject the request.
|
||||
*/
|
||||
export function parseAutoApproveValue(value: unknown): boolean | undefined {
|
||||
if (typeof value === "boolean" || value === undefined) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return value === "true" ? true : value === "false" ? false : undefined;
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
import { Readable, Writable } from "node:stream";
|
||||
import { writeDiagnostic } from "../utils/output";
|
||||
|
||||
export async function runAcpMode(): Promise<void> {
|
||||
export interface AcpModeOptions {
|
||||
autoApproveTools?: boolean;
|
||||
}
|
||||
|
||||
export async function runAcpMode(options?: AcpModeOptions): Promise<void> {
|
||||
const { AgentSideConnection, ndJsonStream } = await import(
|
||||
"@agentclientprotocol/sdk"
|
||||
);
|
||||
@@ -15,7 +19,9 @@ export async function runAcpMode(): Promise<void> {
|
||||
);
|
||||
|
||||
const connection = new AgentSideConnection((conn) => {
|
||||
return new AcpAgent(conn);
|
||||
return new AcpAgent(conn, {
|
||||
autoApproveTools: options?.autoApproveTools,
|
||||
});
|
||||
}, stream);
|
||||
|
||||
// Keep the process alive until the connection closes
|
||||
|
||||
@@ -798,7 +798,10 @@ export async function runCli(): Promise<void> {
|
||||
// Enters the Agent Client Protocol stdio transport and never falls through.
|
||||
if (args.acpMode) {
|
||||
const { runAcpMode } = await import("./acp/index");
|
||||
await runAcpMode();
|
||||
// Only an explicit `--auto-approve true` (or `--yolo`) enables
|
||||
// auto-approval in ACP mode; We do not respect the default to
|
||||
// avoid accidental auto-approval in ACP mode.
|
||||
await runAcpMode({ autoApproveTools: args.autoApproveOverride === true });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ Commands:
|
||||
| `-V, --version` | Output the version number |
|
||||
| `-p, --plan` | Run in plan mode |
|
||||
| `--json` | Output messages as JSON instead of styled text |
|
||||
| `--auto-approve <boolean>` | Set tool auto-approval for all tools (default: `true`) |
|
||||
| `--auto-approve <boolean>` | Set tool auto-approval for all tools (default: `true`; in [ACP mode](/usage/acp#auto-approving-tools) the default is `false`) |
|
||||
| `-t, --timeout <seconds>` | Optional timeout in seconds (default: `0` for no timeout) |
|
||||
| `-m, --model <model-id>` | Model to use for the session with the selected provider |
|
||||
| `-v, --verbose` | Show verbose output |
|
||||
|
||||
+25
-1
@@ -93,11 +93,35 @@ Anything that speaks ACP can run Cline the same way — point it at `cline --acp
|
||||
- **Sign-in from the client** — if no credentials are found, the client prompts you to sign in with Cline, ClinePass, or a ChatGPT subscription. Credentials saved by `cline auth` are reused automatically.
|
||||
- **Plan/Act modes** — switch between [Plan and Act](/core-workflows/plan-and-act) from the client's mode selector.
|
||||
- **Model and provider selection** — pick any model from the active provider's catalog, or switch providers, from the client's model picker.
|
||||
- **Permission prompts** — file edits and commands are approved through the client's permission UI; nothing is auto-approved.
|
||||
- **Permission prompts** — file edits and commands are approved through the client's permission UI. Nothing is auto-approved by default, but you can [auto-approve all tools](#auto-approving-tools) per session or at launch.
|
||||
- **Session resume** — conversations are persisted, so clients that support session loading can restore a thread after a restart.
|
||||
- **Images** — prompts can include images for vision-capable models.
|
||||
- **Organization switching** — Cline team accounts can switch between personal and organization billing.
|
||||
|
||||
## Auto-approving tools
|
||||
|
||||
By default every file edit and command goes through the client's permission UI. To let Cline run tools without asking:
|
||||
|
||||
- **Per session** — sessions expose an **Auto-approve tools** toggle in the client's session settings (clients that support ACP config options, like Zed, render it alongside the model and mode pickers). Flipping it takes effect on the next tool call.
|
||||
- **At launch** — pass `--auto-approve true` in the client's agent config to start every session with auto-approval on:
|
||||
|
||||
```json
|
||||
{
|
||||
"agent_servers": {
|
||||
"Cline": {
|
||||
"type": "custom",
|
||||
"command": "cline",
|
||||
"args": ["--acp", "--auto-approve", "true"],
|
||||
"env": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<Warning>
|
||||
With auto-approval on, Cline edits files and runs commands without asking. Only enable it in workspaces where you're comfortable with unattended changes.
|
||||
</Warning>
|
||||
|
||||
## Environment variables
|
||||
|
||||
Set these under `env` in the client's agent server config to preconfigure the agent:
|
||||
|
||||
Reference in New Issue
Block a user