diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index cc9a029a045c..474295aa9177 100644 --- a/packages/opencode/src/acp/agent.ts +++ b/packages/opencode/src/acp/agent.ts @@ -110,13 +110,27 @@ export namespace ACP { case "permission.asked": { const permission = event.properties const session = this.sessionManager.tryGet(permission.sessionID) - if (!session) return + + // Child sessions (from Task subagents) aren't in sessionManager, so fetch from SDK + let directory = session?.cwd + if (!directory) { + const info = await this.sdk.session + .get({ sessionID: permission.sessionID }) + .then((x) => x.data) + .catch(() => undefined) + directory = info?.directory + } + if (!directory) { + log.warn("cannot handle permission for unknown session", { + sessionID: permission.sessionID, + permissionID: permission.id, + }) + return + } const prev = this.permissionQueues.get(permission.sessionID) ?? Promise.resolve() const next = prev .then(async () => { - const directory = session.cwd - const res = await this.connection .requestPermission({ sessionId: permission.sessionID, @@ -164,7 +178,7 @@ export namespace ACP { if (newContent) { this.connection.writeTextFile({ - sessionId: session.id, + sessionId: permission.sessionID, path: filepath, content: newContent, }) diff --git a/packages/opencode/test/acp/event-subscription.test.ts b/packages/opencode/test/acp/event-subscription.test.ts index 8e139ff59732..1909e836c5d9 100644 --- a/packages/opencode/test/acp/event-subscription.test.ts +++ b/packages/opencode/test/acp/event-subscription.test.ts @@ -345,6 +345,67 @@ describe("acp.agent event subscription", () => { }) }) + test("permission.asked events from child sessions (not in sessionManager) are handled", async () => { + await using tmp = await tmpdir() + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const permissionReplies: string[] = [] + const { agent, controller, stop, sdk } = createFakeAgent() + + const childSessionID = "ses_child_123" + const childDirectory = "/tmp/child-session-dir" + + // Mock session.get to return child session info + sdk.session.get = async (params: any) => { + if (params.sessionID === childSessionID) { + return { + data: { + id: childSessionID, + directory: childDirectory, + time: { created: Date.now() }, + }, + } + } + return { data: undefined } + } + + sdk.permission.reply = async (params: any) => { + permissionReplies.push(params.requestID) + return { data: true } + } + + const cwd = "/tmp/opencode-acp-test" + + // Create a parent session (registered in sessionManager) + await agent.newSession({ cwd, mcpServers: [] } as any) + + // Push permission.asked from a child session (NOT registered in sessionManager) + controller.push({ + directory: childDirectory, + payload: { + type: "permission.asked", + properties: { + id: "perm_child", + sessionID: childSessionID, + permission: "external_directory", + patterns: ["*"], + metadata: { path: "/tmp" }, + always: [], + }, + }, + } as any) + + await new Promise((r) => setTimeout(r, 20)) + + // Permission should be handled even though session is not in sessionManager + expect(permissionReplies).toContain("perm_child") + + stop() + }, + }) + }) + test("permission prompt on session A does not block message updates for session B", async () => { await using tmp = await tmpdir() await Instance.provide({