From 1c88891f66b0d244bb0933772042b8a309614760 Mon Sep 17 00:00:00 2001 From: Bryce Ryan Date: Tue, 10 Feb 2026 14:34:04 -0500 Subject: [PATCH 001/172] Allowing subagents to send tool permissions through the ACP correctly --- packages/opencode/src/acp/agent.ts | 64 +++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index 775acc52a50..33b10597226 100644 --- a/packages/opencode/src/acp/agent.ts +++ b/packages/opencode/src/acp/agent.ts @@ -30,7 +30,7 @@ import { import { Log } from "../util/log" import { ACPSessionManager } from "./session" -import type { ACPConfig } from "./types" +import type { ACPConfig, ACPSessionState } from "./types" import { Provider } from "../provider/provider" import { Agent as AgentModule } from "../agent/agent" import { Installation } from "@/installation" @@ -41,6 +41,7 @@ import { z } from "zod" import { LoadAPIKeyError } from "ai" import type { AssistantMessage, Event, OpencodeClient, SessionMessageResponse } from "@opencode-ai/sdk/v2" import { applyPatch } from "diff" +import type { Session } from "@opencode-ai/sdk" type ModeOption = { id: string; name: string; description?: string } type ModelOption = { modelId: string; name: string } @@ -174,21 +175,66 @@ export namespace ACP { } } + /** + * Resolve session for an event, checking parent sessions if the session is a child (e.g., from task tool). + * Recursively walks up the parent chain to handle nested tasks. + * Returns the ACP session state and the actual event sessionID. + */ + private async getParentACPSessionForEvent(eventSessionID: string): Promise<{ + session: ACPSessionState + eventSessionID: string + } | undefined> { + + let currentSessionID: string | undefined = eventSessionID; + const visited = new Set(); + + // Traverse up the agent call stack until we hit one that is registered as an ACP agent + while(currentSessionID && !visited.has(currentSessionID)) { + visited.add(currentSessionID); + + const session = this.sessionManager.tryGet(currentSessionID) + if(session){ + return { session, eventSessionID } + } + + const sessionInfo: Session | undefined = await this.sdk.session.get( + { + sessionID: currentSessionID, + directory: ".", + }, + { throwOnError: true }, + ) + .then((x) => x.data) + .catch(() => undefined) + + if(!sessionInfo?.parentID){ + break + } + + currentSessionID = sessionInfo.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) - if (!session) return + // Get parent session, in case this was passed from a subagent tool call + const resolved = await this.getParentACPSessionForEvent(permission.sessionID) + if (!resolved) return + const { session, eventSessionID } = resolved - const prev = this.permissionQueues.get(permission.sessionID) ?? Promise.resolve() + const prev = this.permissionQueues.get(eventSessionID) ?? Promise.resolve() const next = prev .then(async () => { const directory = session.cwd const res = await this.connection .requestPermission({ - sessionId: permission.sessionID, + sessionId: session.id, toolCall: { toolCallId: permission.tool?.callID ?? permission.id, status: "pending", @@ -203,7 +249,7 @@ export namespace ACP { log.error("failed to request permission from ACP", { error, permissionID: permission.id, - sessionID: permission.sessionID, + sessionID: eventSessionID, }) await this.sdk.permission.reply({ requestID: permission.id, @@ -250,11 +296,11 @@ export namespace ACP { log.error("failed to handle permission", { error, permissionID: permission.id }) }) .finally(() => { - if (this.permissionQueues.get(permission.sessionID) === next) { - this.permissionQueues.delete(permission.sessionID) + if (this.permissionQueues.get(eventSessionID) === next) { + this.permissionQueues.delete(eventSessionID) } }) - this.permissionQueues.set(permission.sessionID, next) + this.permissionQueues.set(eventSessionID, next) return } From d98009bad5eba37242627b515bdd21182685d2a2 Mon Sep 17 00:00:00 2001 From: Bryce Ryan Date: Tue, 10 Feb 2026 14:51:06 -0500 Subject: [PATCH 002/172] Code cleanup --- packages/opencode/src/acp/agent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index 33b10597226..adde49e2b99 100644 --- a/packages/opencode/src/acp/agent.ts +++ b/packages/opencode/src/acp/agent.ts @@ -207,7 +207,7 @@ export namespace ACP { .then((x) => x.data) .catch(() => undefined) - if(!sessionInfo?.parentID){ + if(!sessionInfo){ break } From 654475b9be7346d50600fff04da70ceba19bba28 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Fri, 6 Feb 2026 07:10:23 -0600 Subject: [PATCH 003/172] fix(app): hide 'open in app' button on narrow viewports --- .../src/components/session/session-header.tsx | 138 +++++++++--------- 1 file changed, 71 insertions(+), 67 deletions(-) diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx index 61bc26e350c..ec2a231e450 100644 --- a/packages/app/src/components/session/session-header.tsx +++ b/packages/app/src/components/session/session-header.tsx @@ -268,74 +268,78 @@ export function SessionHeader() {
- - - {language.t("session.header.open.copyPath")} - - } - > -
- - - -
-
+ class="rounded-sm h-[24px] py-1.5 pr-3 pl-2 gap-2 border-none shadow-none" + onClick={copyPath} + aria-label={language.t("session.header.open.copyPath")} + > + + + {language.t("session.header.open.copyPath")} + + + } + > +
+ + + + + + + {language.t("session.header.openIn")} + { + if (!OPEN_APPS.includes(value as OpenApp)) return + setPrefs("app", value as OpenApp) + }} + > + {options().map((o) => ( + openDir(o.id)}> + + {o.label} + + + + + ))} + + + + + + + {language.t("session.header.open.copyPath")} + + + + + +
+
+
From d4e3f5e4cdd3cc38857fa938701bccd1d6c9a7db Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Fri, 6 Feb 2026 08:54:51 -0600 Subject: [PATCH 004/172] feat(web): i18n (#12471) --- packages/console/app/src/app.tsx | 20 +- .../app/src/component/email-signup.tsx | 14 +- packages/console/app/src/component/footer.tsx | 16 +- packages/console/app/src/component/header.tsx | 66 +- .../app/src/component/language-picker.css | 135 ++++ .../app/src/component/language-picker.tsx | 34 + packages/console/app/src/component/legal.tsx | 12 +- packages/console/app/src/context/i18n.tsx | 27 + packages/console/app/src/context/language.tsx | 68 ++ packages/console/app/src/entry-server.tsx | 39 +- packages/console/app/src/i18n/ar.ts | 531 ++++++++++++++++ packages/console/app/src/i18n/br.ts | 466 ++++++++++++++ packages/console/app/src/i18n/da.ts | 463 ++++++++++++++ packages/console/app/src/i18n/de.ts | 468 ++++++++++++++ packages/console/app/src/i18n/en.ts | 591 ++++++++++++++++++ packages/console/app/src/i18n/es.ts | 465 ++++++++++++++ packages/console/app/src/i18n/fr.ts | 471 ++++++++++++++ packages/console/app/src/i18n/index.ts | 43 ++ packages/console/app/src/i18n/it.ts | 464 ++++++++++++++ packages/console/app/src/i18n/ja.ts | 502 +++++++++++++++ packages/console/app/src/i18n/ko.ts | 494 +++++++++++++++ packages/console/app/src/i18n/no.ts | 462 ++++++++++++++ packages/console/app/src/i18n/pl.ts | 466 ++++++++++++++ packages/console/app/src/i18n/ru.ts | 539 ++++++++++++++++ packages/console/app/src/i18n/th.ts | 541 ++++++++++++++++ packages/console/app/src/i18n/tr.ts | 467 ++++++++++++++ packages/console/app/src/i18n/zh.ts | 473 ++++++++++++++ packages/console/app/src/i18n/zht.ts | 473 ++++++++++++++ packages/console/app/src/lib/form-error.ts | 83 +++ packages/console/app/src/lib/language.ts | 175 ++++++ packages/console/app/src/routes/[...404].tsx | 14 +- .../console/app/src/routes/bench/[id].tsx | 74 ++- .../console/app/src/routes/bench/index.tsx | 12 +- packages/console/app/src/routes/black.tsx | 41 +- .../console/app/src/routes/black/common.tsx | 13 +- .../console/app/src/routes/black/index.tsx | 34 +- .../app/src/routes/black/subscribe/[plan].tsx | 80 ++- .../app/src/routes/black/workspace.tsx | 41 +- .../console/app/src/routes/brand/index.css | 1 + .../console/app/src/routes/brand/index.tsx | 12 +- .../app/src/routes/changelog/index.css | 1 + .../app/src/routes/changelog/index.tsx | 20 +- .../console/app/src/routes/docs/[...path].ts | 8 +- packages/console/app/src/routes/docs/index.ts | 8 +- .../console/app/src/routes/download/index.css | 1 + .../console/app/src/routes/download/index.tsx | 112 ++-- .../app/src/routes/enterprise/index.css | 1 + .../app/src/routes/enterprise/index.tsx | 61 +- packages/console/app/src/routes/index.css | 1 + packages/console/app/src/routes/index.tsx | 143 ++--- packages/console/app/src/routes/s/[id].ts | 8 +- .../console/app/src/routes/t/[...path].tsx | 8 +- packages/console/app/src/routes/temp.tsx | 45 +- packages/console/app/src/routes/user-menu.tsx | 4 +- .../app/src/routes/workspace-picker.tsx | 14 +- .../console/app/src/routes/workspace/[id].css | 29 + .../console/app/src/routes/workspace/[id].tsx | 28 +- .../[id]/billing/billing-section.tsx | 35 +- .../workspace/[id]/billing/black-section.tsx | 62 +- .../[id]/billing/monthly-limit-section.tsx | 40 +- .../[id]/billing/payment-section.tsx | 25 +- .../workspace/[id]/billing/reload-section.tsx | 46 +- .../routes/workspace/[id]/graph-section.tsx | 44 +- .../app/src/routes/workspace/[id]/index.tsx | 12 +- .../workspace/[id]/keys/key-section.tsx | 39 +- .../workspace/[id]/members/member-section.tsx | 109 ++-- .../workspace/[id]/members/role-dropdown.tsx | 6 +- .../routes/workspace/[id]/model-section.tsx | 16 +- .../workspace/[id]/new-user-section.tsx | 30 +- .../workspace/[id]/provider-section.tsx | 40 +- .../[id]/settings/settings-section.tsx | 27 +- .../routes/workspace/[id]/usage-section.tsx | 34 +- .../app/src/routes/workspace/common.tsx | 2 +- packages/console/app/src/routes/zen/index.css | 1 + packages/console/app/src/routes/zen/index.tsx | 120 ++-- 75 files changed, 9856 insertions(+), 714 deletions(-) create mode 100644 packages/console/app/src/component/language-picker.css create mode 100644 packages/console/app/src/component/language-picker.tsx create mode 100644 packages/console/app/src/context/i18n.tsx create mode 100644 packages/console/app/src/context/language.tsx create mode 100644 packages/console/app/src/i18n/ar.ts create mode 100644 packages/console/app/src/i18n/br.ts create mode 100644 packages/console/app/src/i18n/da.ts create mode 100644 packages/console/app/src/i18n/de.ts create mode 100644 packages/console/app/src/i18n/en.ts create mode 100644 packages/console/app/src/i18n/es.ts create mode 100644 packages/console/app/src/i18n/fr.ts create mode 100644 packages/console/app/src/i18n/index.ts create mode 100644 packages/console/app/src/i18n/it.ts create mode 100644 packages/console/app/src/i18n/ja.ts create mode 100644 packages/console/app/src/i18n/ko.ts create mode 100644 packages/console/app/src/i18n/no.ts create mode 100644 packages/console/app/src/i18n/pl.ts create mode 100644 packages/console/app/src/i18n/ru.ts create mode 100644 packages/console/app/src/i18n/th.ts create mode 100644 packages/console/app/src/i18n/tr.ts create mode 100644 packages/console/app/src/i18n/zh.ts create mode 100644 packages/console/app/src/i18n/zht.ts create mode 100644 packages/console/app/src/lib/form-error.ts create mode 100644 packages/console/app/src/lib/language.ts diff --git a/packages/console/app/src/app.tsx b/packages/console/app/src/app.tsx index cde2f01876f..3d16a64ab7c 100644 --- a/packages/console/app/src/app.tsx +++ b/packages/console/app/src/app.tsx @@ -6,19 +6,25 @@ import { Favicon } from "@opencode-ai/ui/favicon" import { Font } from "@opencode-ai/ui/font" import "@ibm/plex/css/ibm-plex.css" import "./app.css" +import { LanguageProvider } from "~/context/language" +import { I18nProvider } from "~/context/i18n" export default function App() { return ( ( - - opencode - - - - {props.children} - + + + + opencode + + + + {props.children} + + + )} > diff --git a/packages/console/app/src/component/email-signup.tsx b/packages/console/app/src/component/email-signup.tsx index 65f81b5fc6d..bd33e92006a 100644 --- a/packages/console/app/src/component/email-signup.tsx +++ b/packages/console/app/src/component/email-signup.tsx @@ -2,6 +2,7 @@ import { action, useSubmission } from "@solidjs/router" import dock from "../asset/lander/dock.png" import { Resource } from "@opencode-ai/console-resource" import { Show } from "solid-js" +import { useI18n } from "~/context/i18n" const emailSignup = action(async (formData: FormData) => { "use server" @@ -23,22 +24,21 @@ const emailSignup = action(async (formData: FormData) => { export function EmailSignup() { const submission = useSubmission(emailSignup) + const i18n = useI18n() return (
-

Be the first to know when we release new products

-

Join the waitlist for early access.

+

{i18n.t("email.title")}

+

{i18n.t("email.subtitle")}

- +
-
- Almost done, check your inbox and confirm your email address -
+
{i18n.t("email.success")}
{submission.error}
diff --git a/packages/console/app/src/component/footer.tsx b/packages/console/app/src/component/footer.tsx index 27f8ddd65f1..45dae87ecfe 100644 --- a/packages/console/app/src/component/footer.tsx +++ b/packages/console/app/src/component/footer.tsx @@ -2,12 +2,16 @@ import { createAsync } from "@solidjs/router" import { createMemo } from "solid-js" import { github } from "~/lib/github" import { config } from "~/config" +import { useLanguage } from "~/context/language" +import { useI18n } from "~/context/i18n" export function Footer() { + const language = useLanguage() + const i18n = useI18n() const githubData = createAsync(() => github()) const starCount = createMemo(() => githubData()?.stars - ? new Intl.NumberFormat("en-US", { + ? new Intl.NumberFormat(language.tag(language.locale()), { notation: "compact", compactDisplay: "short", }).format(githubData()!.stars!) @@ -18,20 +22,20 @@ export function Footer() { ) diff --git a/packages/console/app/src/component/header.tsx b/packages/console/app/src/component/header.tsx index 72e9d04189c..3eca8b88c11 100644 --- a/packages/console/app/src/component/header.tsx +++ b/packages/console/app/src/component/header.tsx @@ -19,6 +19,7 @@ import { createStore } from "solid-js/store" import { github } from "~/lib/github" import { createEffect, onCleanup } from "solid-js" import { config } from "~/config" +import { useI18n } from "~/context/i18n" import "./header-context-menu.css" const isDarkMode = () => window.matchMedia("(prefers-color-scheme: dark)").matches @@ -36,12 +37,14 @@ const fetchSvgContent = async (svgPath: string): Promise => { export function Header(props: { zen?: boolean; hideGetStarted?: boolean }) { const navigate = useNavigate() + const i18n = useI18n() const githubData = createAsync(() => github()) const starCount = createMemo(() => githubData()?.stars ? new Intl.NumberFormat("en-US", { notation: "compact", compactDisplay: "short", + maximumFractionDigits: 0, }).format(githubData()?.stars!) : config.github.starsFormatted.compact, ) @@ -119,8 +122,8 @@ export function Header(props: { zen?: boolean; hideGetStarted?: boolean }) {
@@ -130,49 +133,56 @@ export function Header(props: { zen?: boolean; hideGetStarted?: boolean }) { style={`left: ${store.contextMenuPosition.x}px; top: ${store.contextMenuPosition.y}px;`} >