diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts index f3781f1abd86..4710e80f570a 100644 --- a/packages/opencode/src/cli/cmd/run.ts +++ b/packages/opencode/src/cli/cmd/run.ts @@ -297,6 +297,11 @@ export const RunCommand = cmd({ describe: "show thinking blocks", default: false, }) + .option("auto-approve", { + type: "boolean", + describe: "auto-approve permission requests (use --no-auto-approve to reject instead)", + default: true, + }) }, handler: async (args) => { let message = [...args.message, ...(args["--"] || [])] @@ -539,14 +544,15 @@ export const RunCommand = cmd({ if (event.type === "permission.asked") { const permission = event.properties if (permission.sessionID !== sessionID) continue + const reply = args["auto-approve"] ? "once" : "reject" UI.println( UI.Style.TEXT_WARNING_BOLD + "!", UI.Style.TEXT_NORMAL + - `permission requested: ${permission.permission} (${permission.patterns.join(", ")}); auto-rejecting`, + `permission requested: ${permission.permission} (${permission.patterns.join(", ")}); auto-${reply === "once" ? "approving" : "rejecting"}`, ) await sdk.permission.reply({ requestID: permission.id, - reply: "reject", + reply, }) } } diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index 4e42fb0d2ec7..427a1ad52747 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -39,6 +39,7 @@ export namespace LLM { tools: Record retries?: number toolChoice?: "auto" | "required" | "none" + permission?: PermissionNext.Ruleset } export type StreamOutput = StreamTextResult @@ -255,8 +256,11 @@ export namespace LLM { }) } - async function resolveTools(input: Pick) { - const disabled = PermissionNext.disabled(Object.keys(input.tools), input.agent.permission) + async function resolveTools(input: Pick) { + const disabled = PermissionNext.disabled( + Object.keys(input.tools), + PermissionNext.merge(input.agent.permission, input.permission ?? []), + ) for (const tool of Object.keys(input.tools)) { if (input.user.tools?.[tool] === false || disabled.has(tool)) { delete input.tools[tool] diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 75bd3c9dfaca..785487645992 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -674,6 +674,7 @@ export namespace SessionPrompt { tools, model, toolChoice: format.type === "json_schema" ? "required" : undefined, + permission: session.permission ?? [], }) // If structured output was captured, save it and exit immediately diff --git a/packages/opencode/src/tool/question.ts b/packages/opencode/src/tool/question.ts index a2887546d4bc..7ee1680240b7 100644 --- a/packages/opencode/src/tool/question.ts +++ b/packages/opencode/src/tool/question.ts @@ -1,6 +1,7 @@ import z from "zod" import { Tool } from "./tool" import { Question } from "../question" +import { PermissionNext } from "../permission/next" import DESCRIPTION from "./question.txt" export const QuestionTool = Tool.define("question", { @@ -9,6 +10,19 @@ export const QuestionTool = Tool.define("question", { questions: z.array(Question.Info.omit({ custom: true })).describe("Questions to ask"), }), async execute(params, ctx) { + try { + await ctx.ask({ permission: "question", patterns: ["*"], metadata: {}, always: ["*"] }) + } catch (e) { + if (e instanceof PermissionNext.DeniedError || e instanceof PermissionNext.RejectedError) { + return { + title: "Question denied", + output: "Cannot ask questions in this session. Make your best judgment and proceed.", + metadata: { answers: [] }, + } + } + throw e + } + const answers = await Question.ask({ sessionID: ctx.sessionID, questions: params.questions,