mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-11-04 08:22:03 +00:00
refactor: serve permission requests on the experimental ACP v2 surface (#1234)
First part of step 5 of opt-in ACP v2 support (after #1231, #1232): the v2 surface now sends the agent's permission requests. Nothing changes on ACP v1: no v1 golden file changes. ## What differs in v2 A v1 request asks about a tool call, and clients show the tool call's title as the prompt. A v2 request gives the prompt a required `title` and an optional `description` of its own, apart from the tool call. It asks about a `subject`, which can be a tool call or a command. ## Change - **Prompt text.** The agent already computes the prompt's heading and explanation, but only AIR gets them, in `_meta.jetbrains.air.permission`. The agent's request type, `AcpPermissionRequest`, now carries them as `title` and `description` for a v2 client. It's the same rule #1232 uses for diffs: build what v1 can't express only for a v2 client. So v1 requests are byte-identical, and the v1 scenario golden files, which record what the agent hands `requestPermission`, don't change. The v2 surface fails on a request without a title, so a missed site fails loudly. - **Subject.** Always `{ type: "tool_call", toolCall }`, where `toolCall` is the same patch a `tool_call_update` carries (`v2ToolCallPatch`). The `command` subject would suit Bash once display terminals exist, since its `terminalId` refers to one. - **Approval preview.** The exact patch before an Edit or Write runs is now built in v2 form for a v2 client, and the request shows it. This fixed a real gap: the agent pins the preview as content the client holds, but only AIR's request shape included it. Without the fix, a v2 client saw no diff at all, because the hook's identical patch was then dropped as unchanged. - **Answer.** v2's outcomes map back unchanged. An outcome v1 doesn't know, which the draft says is no approval, becomes `cancelled`, and the agent reads that as an aborted tool use. - **`requires_action`** while a request is open was already in place since #1226. ## Scenario suite All 12 scenarios that stopped at a permission request now pass: Bash, Edit (approved and rejected), Write, NotebookEdit, WebFetch, MCP, network, ExitPlanMode (approved and rejected), and two subagent scenarios. Each recording shows `requires_action`, the request, then `running`. Now only replay and the two plan scenarios remain. ## Verification - **End to end:** a new test drives the agent's real `canUseTool` through a v2 client. Allow, reject, and an unknown outcome become allow, deny, and an aborted tool use. - **Unit tests:** the prompt's title is separate from the tool call's (ExitPlanMode: "Ready to code?" vs "Approve Plan"), the preview shows in the request, the v2 preview fallback works, and the title guard rejects a missing title. - **Mutations:** each rule fails a test when removed. Sending `title`/`description` to every client fails 37 tests, including v1 golden files. - Build, lint and all 2380 tests pass.
This commit is contained in:
@@ -556,6 +556,29 @@ describe("ACP v2 diffs", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("previews a change in v2 form", async () => {
|
||||
const filePath = await temporaryFile("a\n");
|
||||
expect(
|
||||
await previewPatchContent("Write", { file_path: filePath, content: "b\n" }, undefined, "v2"),
|
||||
).toEqual([
|
||||
v2Diff(
|
||||
filePath,
|
||||
"modify",
|
||||
`diff --git ${filePath} ${filePath}\n--- ${filePath}\n+++ ${filePath}\n@@ -1 +1 @@\n-a\n+b\n`,
|
||||
),
|
||||
]);
|
||||
// A text that cannot have an exact patch: the change alone, where v1 gets
|
||||
// the standard diff.
|
||||
expect(
|
||||
await previewPatchContent(
|
||||
"Write",
|
||||
{ file_path: filePath, content: "b\r\n" },
|
||||
undefined,
|
||||
"v2",
|
||||
),
|
||||
).toEqual([v2Diff(filePath, "modify")]);
|
||||
});
|
||||
|
||||
it("sends no diff where the operation is unknown or nothing changed", async () => {
|
||||
const filePath = await temporaryFile("x\n");
|
||||
// An Edit with an empty old_string created the file or filled an empty one.
|
||||
|
||||
Reference in New Issue
Block a user