From 77a53ab9053fb0fbd5e750748dd910b4be20756d Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Wed, 22 Apr 2026 00:35:48 -0400 Subject: [PATCH 1/2] feat: update codex plugin to support 5.5 (#23789) --- packages/opencode/src/plugin/codex.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/opencode/src/plugin/codex.ts b/packages/opencode/src/plugin/codex.ts index 337a4e91f0ad..b9dcb1cc454f 100644 --- a/packages/opencode/src/plugin/codex.ts +++ b/packages/opencode/src/plugin/codex.ts @@ -374,6 +374,7 @@ export async function CodexAuthPlugin(input: PluginInput): Promise { "gpt-5.3-codex", "gpt-5.4", "gpt-5.4-mini", + "gpt-5.5", ]) for (const [modelId, model] of Object.entries(provider.models)) { if (modelId.includes("codex")) continue From f917dc30218d854eef5acd3a7e7fd60e1322297a Mon Sep 17 00:00:00 2001 From: Jason Johnson Date: Wed, 22 Apr 2026 10:51:41 -0400 Subject: [PATCH 2/2] fix(acp): forward subagent session events to ACP client When a prompt is run through the Task tool, events arrive with a child session ID that is not directly tracked by ACPSessionManager. Add resolveRootSession() which walks the parentID chain via the SDK to find the nearest ACP-tracked ancestor, caching results to avoid repeated lookups. Replace all sessionManager.tryGet() calls in handleEvent with resolveRootSession() so that tool call and message delta events from sub-agent sessions are correctly forwarded to the ACP client. --- packages/opencode/src/acp/agent.ts | 59 +++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index aff523a7e9d3..de17a80e9c3f 100644 --- a/packages/opencode/src/acp/agent.ts +++ b/packages/opencode/src/acp/agent.ts @@ -36,7 +36,7 @@ import { pathToFileURL } from "url" import { Filesystem } from "../util" import { Hash } from "@opencode-ai/core/util/hash" import { ACPSessionManager } from "./session" -import type { ACPConfig } from "./types" +import type { ACPConfig, ACPSessionState } from "./types" import { Provider } from "../provider" import { ModelID, ProviderID } from "../provider/schema" import { Agent as AgentModule } from "../agent/agent" @@ -147,6 +147,8 @@ export class Agent implements ACPAgent { private bashSnapshots = new Map() private toolStarts = new Set() private permissionQueues = new Map>() + // Maps child session IDs to their root ACP-tracked session ID + private childToRootSession = new Map() private permissionOptions: PermissionOption[] = [ { optionId: "once", kind: "allow_once", name: "Allow once" }, { optionId: "always", kind: "allow_always", name: "Always allow" }, @@ -187,11 +189,55 @@ export class Agent implements ACPAgent { } } + /** + * Given a session ID, returns the ACPSessionState for that session or the + * nearest ACP-tracked ancestor. Child sessions created by the Task tool + * have a parentID chain leading back to the root ACP session. We cache + * the mapping so we only traverse the chain once per child session. + */ + private async resolveRootSession(sessionID: string): Promise { + // Fast path: directly tracked by ACP session manager + const direct = this.sessionManager.tryGet(sessionID) + if (direct) return direct + + // Check cache + const cached = this.childToRootSession.get(sessionID) + if (cached) return this.sessionManager.tryGet(cached) + + // Walk the parentID chain via the SDK until we find a known root session + let currentID = sessionID + const visited: string[] = [] + while (true) { + visited.push(currentID) + const info = await this.sdk.session + .get({ sessionID: currentID, directory: "" }, { throwOnError: false }) + .then((x) => x.data) + .catch(() => undefined) + + if (!info) break + + const parentID = info.parentID + if (!parentID) break + + const root = this.sessionManager.tryGet(parentID) + if (root) { + // Cache the mapping for all visited IDs + for (const id of visited) { + this.childToRootSession.set(id, root.id) + } + return root + } + currentID = parentID + } + + return undefined + } + private async handleEvent(event: Event) { switch (event.type) { case "permission.asked": { const permission = event.properties - const session = this.sessionManager.tryGet(permission.sessionID) + const session = await this.resolveRootSession(permission.sessionID) if (!session) return const prev = this.permissionQueues.get(permission.sessionID) ?? Promise.resolve() @@ -274,7 +320,7 @@ export class Agent implements ACPAgent { log.info("message part updated", { event: event.properties }) const props = event.properties const part = props.part - const session = this.sessionManager.tryGet(part.sessionID) + const session = await this.resolveRootSession(part.sessionID) if (!session) return const sessionId = session.id @@ -466,16 +512,19 @@ export class Agent implements ACPAgent { case "message.part.delta": { const props = event.properties - const session = this.sessionManager.tryGet(props.sessionID) + const session = await this.resolveRootSession(props.sessionID) if (!session) return const sessionId = session.id + // Use the child session's own cwd for SDK lookups (may differ from root) + const childSession = this.sessionManager.tryGet(props.sessionID) + const directory = childSession?.cwd ?? session.cwd const message = await this.sdk.session .message( { sessionID: props.sessionID, messageID: props.messageID, - directory: session.cwd, + directory, }, { throwOnError: true }, )