From 05f0214a256a21f23b8162994c77187debb5938e Mon Sep 17 00:00:00 2001 From: "junqian.hjc" Date: Tue, 17 Mar 2026 14:02:50 +0800 Subject: [PATCH] fix(acp): handle question.asked event to prevent hanging When OpenCode runs via ACP, the question tool would hang indefinitely because the question.asked event was not handled in the ACP agent. This change adds a handler for question.asked that: - Converts question options to ACP requestPermission options - Forwards the question to the ACP client via requestPermission - Maps the response back to question answers - Calls the SDK question.reply to unblock the tool Fixes #17920 --- packages/opencode/src/acp/agent.ts | 60 ++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index 2a6bbbb1e444..066445d72abd 100644 --- a/packages/opencode/src/acp/agent.ts +++ b/packages/opencode/src/acp/agent.ts @@ -264,6 +264,66 @@ export namespace ACP { return } + case "question.asked": { + const question = event.properties + const session = this.sessionManager.tryGet(question.sessionID) + if (!session) return + + const directory = session.cwd + const firstQuestion = question.questions[0] + if (!firstQuestion) return + + const options: PermissionOption[] = firstQuestion.options.map((opt) => ({ + optionId: opt.label, + name: opt.label, + kind: "allow_once" as const, + })) + + const res = await this.connection + .requestPermission({ + sessionId: question.sessionID, + toolCall: { + toolCallId: question.tool?.callID ?? question.id, + status: "pending", + title: firstQuestion.question, + rawInput: { + questions: question.questions, + }, + kind: "other", + }, + options, + }) + .catch(async (error) => { + log.error("failed to request permission from ACP for question", { + error, + questionID: question.id, + sessionID: question.sessionID, + }) + await this.sdk.question.reject({ + requestID: question.id, + directory, + }) + return undefined + }) + + if (!res) return + if (res.outcome.outcome !== "selected") { + await this.sdk.question.reject({ + requestID: question.id, + directory, + }) + return + } + + const answers = [[res.outcome.optionId!]] + await this.sdk.question.reply({ + requestID: question.id, + directory, + answers, + }) + return + } + case "message.part.updated": { log.info("message part updated", { event: event.properties }) const props = event.properties