From 3a0c73f598b388c319e43a7e45e6e729b39fece6 Mon Sep 17 00:00:00 2001 From: Bryan Dady Date: Fri, 22 May 2026 12:29:16 -0600 Subject: [PATCH 1/5] fix(acp): include shell command in permission request metadata and title --- packages/opencode/src/acp/agent.ts | 5 ++++- packages/opencode/src/tool/shell.ts | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index 8b74b9c9bad3..c02cd105a439 100644 --- a/packages/opencode/src/acp/agent.ts +++ b/packages/opencode/src/acp/agent.ts @@ -205,7 +205,10 @@ export class Agent implements ACPAgent { toolCall: { toolCallId: permission.tool?.callID ?? permission.id, status: "pending", - title: permission.permission, + title: + typeof permission.metadata?.command === "string" + ? permission.metadata.command + : permission.permission, rawInput: permission.metadata, kind: toToolKind(permission.permission), locations: toLocations(permission.permission, permission.metadata), diff --git a/packages/opencode/src/tool/shell.ts b/packages/opencode/src/tool/shell.ts index 506d98466e76..ffcaee09185e 100644 --- a/packages/opencode/src/tool/shell.ts +++ b/packages/opencode/src/tool/shell.ts @@ -263,7 +263,7 @@ const parse = Effect.fn("ShellTool.parse")(function* (command: string, ps: boole return tree }) -const ask = Effect.fn("ShellTool.ask")(function* (ctx: Tool.Context, scan: Scan) { +const ask = Effect.fn("ShellTool.ask")(function* (ctx: Tool.Context, scan: Scan, command: string) { if (scan.dirs.size > 0) { const globs = Array.from(scan.dirs).map((dir) => { if (process.platform === "win32") return AppFileSystem.normalizePathPattern(path.join(dir, "*")) @@ -273,7 +273,7 @@ const ask = Effect.fn("ShellTool.ask")(function* (ctx: Tool.Context, scan: Scan) permission: "external_directory", patterns: globs, always: globs, - metadata: {}, + metadata: { command }, }) } @@ -282,7 +282,7 @@ const ask = Effect.fn("ShellTool.ask")(function* (ctx: Tool.Context, scan: Scan) permission: ShellID.ToolID, patterns: Array.from(scan.patterns), always: Array.from(scan.always), - metadata: {}, + metadata: { command }, }) }) @@ -625,7 +625,7 @@ export const ShellTool = Tool.define( ) const scan = yield* collect(tree.rootNode, cwd, ps, shell, instanceCtx) if (!containsPath(cwd, instanceCtx)) scan.dirs.add(cwd) - yield* ask(ctx, scan) + yield* ask(ctx, scan, params.command) }), ) From 7c1a3dffe51b73ef3b956e021ed8d643753ca40c Mon Sep 17 00:00:00 2001 From: Bryan Dady Date: Fri, 22 May 2026 13:38:34 -0600 Subject: [PATCH 2/5] fix(acp): prefer description over command in shell permission prompt title --- packages/opencode/src/acp/agent.ts | 8 +++++--- packages/opencode/src/tool/shell.ts | 13 +++++++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index c02cd105a439..0c7076b9926e 100644 --- a/packages/opencode/src/acp/agent.ts +++ b/packages/opencode/src/acp/agent.ts @@ -206,9 +206,11 @@ export class Agent implements ACPAgent { toolCallId: permission.tool?.callID ?? permission.id, status: "pending", title: - typeof permission.metadata?.command === "string" - ? permission.metadata.command - : permission.permission, + typeof permission.metadata?.description === "string" + ? permission.metadata.description + : typeof permission.metadata?.command === "string" + ? permission.metadata.command + : permission.permission, rawInput: permission.metadata, kind: toToolKind(permission.permission), locations: toLocations(permission.permission, permission.metadata), diff --git a/packages/opencode/src/tool/shell.ts b/packages/opencode/src/tool/shell.ts index ffcaee09185e..2a98e2e963e8 100644 --- a/packages/opencode/src/tool/shell.ts +++ b/packages/opencode/src/tool/shell.ts @@ -263,7 +263,12 @@ const parse = Effect.fn("ShellTool.parse")(function* (command: string, ps: boole return tree }) -const ask = Effect.fn("ShellTool.ask")(function* (ctx: Tool.Context, scan: Scan, command: string) { +const ask = Effect.fn("ShellTool.ask")(function* ( + ctx: Tool.Context, + scan: Scan, + command: string, + description: string | undefined, +) { if (scan.dirs.size > 0) { const globs = Array.from(scan.dirs).map((dir) => { if (process.platform === "win32") return AppFileSystem.normalizePathPattern(path.join(dir, "*")) @@ -273,7 +278,7 @@ const ask = Effect.fn("ShellTool.ask")(function* (ctx: Tool.Context, scan: Scan, permission: "external_directory", patterns: globs, always: globs, - metadata: { command }, + metadata: { command, description }, }) } @@ -282,7 +287,7 @@ const ask = Effect.fn("ShellTool.ask")(function* (ctx: Tool.Context, scan: Scan, permission: ShellID.ToolID, patterns: Array.from(scan.patterns), always: Array.from(scan.always), - metadata: { command }, + metadata: { command, description }, }) }) @@ -625,7 +630,7 @@ export const ShellTool = Tool.define( ) const scan = yield* collect(tree.rootNode, cwd, ps, shell, instanceCtx) if (!containsPath(cwd, instanceCtx)) scan.dirs.add(cwd) - yield* ask(ctx, scan, params.command) + yield* ask(ctx, scan, params.command, params.description) }), ) From 3ad0f6b2acbb458d0de4e3d1deb0664c4176693e Mon Sep 17 00:00:00 2001 From: Bryan Dady Date: Fri, 22 May 2026 14:20:26 -0600 Subject: [PATCH 3/5] fix(acp): use tool description as title in permission prompts and tool call updates --- packages/opencode/src/acp/agent.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index 0c7076b9926e..e28d1be2d988 100644 --- a/packages/opencode/src/acp/agent.ts +++ b/packages/opencode/src/acp/agent.ts @@ -306,7 +306,7 @@ export class Agent implements ACPAgent { toolCallId: part.callID, status: "in_progress", kind: toToolKind(part.tool), - title: part.tool, + title: (part.state.input as { description?: string })?.description || part.tool, locations: toLocations(part.tool, part.state.input), rawInput: part.state.input, }, @@ -334,7 +334,7 @@ export class Agent implements ACPAgent { toolCallId: part.callID, status: "in_progress", kind: toToolKind(part.tool), - title: part.tool, + title: (part.state.input as { description?: string })?.description || part.tool, locations: toLocations(part.tool, part.state.input), rawInput: part.state.input, ...(content.length > 0 && { content }), @@ -408,7 +408,7 @@ export class Agent implements ACPAgent { toolCallId: part.callID, status: "failed", kind: toToolKind(part.tool), - title: part.tool, + title: (part.state.input as { description?: string })?.description || part.tool, rawInput: part.state.input, content: [ { @@ -819,7 +819,7 @@ export class Agent implements ACPAgent { toolCallId: part.callID, status: "in_progress", kind: toToolKind(part.tool), - title: part.tool, + title: (part.state.input as { description?: string })?.description || part.tool, locations: toLocations(part.tool, part.state.input), rawInput: part.state.input, ...(runningContent.length > 0 && { content: runningContent }), @@ -891,7 +891,7 @@ export class Agent implements ACPAgent { toolCallId: part.callID, status: "failed", kind: toToolKind(part.tool), - title: part.tool, + title: (part.state.input as { description?: string })?.description || part.tool, rawInput: part.state.input, content: [ { @@ -1053,7 +1053,7 @@ export class Agent implements ACPAgent { update: { sessionUpdate: "tool_call", toolCallId: part.callID, - title: part.tool, + title: (part.state.input as { description?: string })?.description || part.tool, kind: toToolKind(part.tool), status: "pending", locations: [], From 1928588d4a534d319fac3ae1744db84cd54d6342 Mon Sep 17 00:00:00 2001 From: Bryan Dady Date: Fri, 22 May 2026 14:26:32 -0600 Subject: [PATCH 4/5] fix(acp): surface file path and tool context in permission prompt titles --- packages/opencode/src/acp/agent.ts | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index e28d1be2d988..1b682643a220 100644 --- a/packages/opencode/src/acp/agent.ts +++ b/packages/opencode/src/acp/agent.ts @@ -205,12 +205,7 @@ export class Agent implements ACPAgent { toolCall: { toolCallId: permission.tool?.callID ?? permission.id, status: "pending", - title: - typeof permission.metadata?.description === "string" - ? permission.metadata.description - : typeof permission.metadata?.command === "string" - ? permission.metadata.command - : permission.permission, + title: toPermissionTitle(permission.permission, permission.metadata), rawInput: permission.metadata, kind: toToolKind(permission.permission), locations: toLocations(permission.permission, permission.metadata), @@ -1584,7 +1579,9 @@ function toLocations(toolName: string, input: Record): { path: stri case "read": case "edit": case "write": - return input["filePath"] ? [{ path: input["filePath"] }] : [] + return input["filePath"] || input["filepath"] + ? [{ path: input["filePath"] ?? input["filepath"] }] + : [] case "glob": case "grep": return input["path"] ? [{ path: input["path"] }] : [] @@ -1599,6 +1596,21 @@ function toLocations(toolName: string, input: Record): { path: stri } } +function toPermissionTitle(permission: string, metadata: Record): string { + const str = (v: unknown): string | undefined => (typeof v === "string" && v ? v : undefined) + return ( + str(metadata.description) ?? + str(metadata.command) ?? + str(metadata.filepath) ?? + str(metadata.filePath) ?? + str(metadata.pattern) ?? + str(metadata.url) ?? + str(metadata.repository) ?? + str(metadata.path) ?? + permission + ) +} + function completedToolContent(part: ToolPart, kind: ToolKind): ToolCallContent[] { if (part.state.status !== "completed") return [] From ceee60b8d00fd064e4c3130b30c0af3c60aea8eb Mon Sep 17 00:00:00 2001 From: Bryan Dady Date: Fri, 22 May 2026 18:18:44 -0600 Subject: [PATCH 5/5] fix(acp): show always-allow scope in permission prompt option label --- packages/opencode/src/acp/agent.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index 1b682643a220..6313e5acb579 100644 --- a/packages/opencode/src/acp/agent.ts +++ b/packages/opencode/src/acp/agent.ts @@ -147,11 +147,6 @@ export class Agent implements ACPAgent { private shellSnapshots = new Map() private toolStarts = new Set() private permissionQueues = new Map>() - private permissionOptions: PermissionOption[] = [ - { optionId: "once", kind: "allow_once", name: "Allow once" }, - { optionId: "always", kind: "allow_always", name: "Always allow" }, - { optionId: "reject", kind: "reject_once", name: "Reject" }, - ] constructor(connection: AgentSideConnection, config: ACPConfig) { this.connection = connection @@ -210,7 +205,7 @@ export class Agent implements ACPAgent { kind: toToolKind(permission.permission), locations: toLocations(permission.permission, permission.metadata), }, - options: this.permissionOptions, + options: toPermissionOptions(permission.always), }) .catch(async (error) => { log.error("failed to request permission from ACP", { @@ -1596,6 +1591,18 @@ function toLocations(toolName: string, input: Record): { path: stri } } +function toPermissionOptions(always: string[] | undefined): PermissionOption[] { + const alwaysName = + always && always.length > 0 + ? `Always allow: ${always.join(", ")}` + : "Always allow" + return [ + { optionId: "once", kind: "allow_once", name: "Allow once" }, + { optionId: "always", kind: "allow_always", name: alwaysName }, + { optionId: "reject", kind: "reject_once", name: "Reject" }, + ] +} + function toPermissionTitle(permission: string, metadata: Record): string { const str = (v: unknown): string | undefined => (typeof v === "string" && v ? v : undefined) return (