From b676c4fe0e382aebef2354456f3cd4a81cb22c68 Mon Sep 17 00:00:00 2001 From: LukeParkerDev <10430890+Hona@users.noreply.github.com> Date: Wed, 2 Sep 2026 19:33:15 +1000 Subject: [PATCH] fix(app): keep the browser attachment and tab menu reliable --- packages/app/src/session/browser/model.ts | 17 ++++++++++++++++- .../src/session/files/session-side-panel.tsx | 3 +++ packages/desktop/src/renderer/platform/index.ts | 2 ++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/app/src/session/browser/model.ts b/packages/app/src/session/browser/model.ts index 2dc02656968..2022119633f 100644 --- a/packages/app/src/session/browser/model.ts +++ b/packages/app/src/session/browser/model.ts @@ -42,10 +42,24 @@ export function createSessionBrowser(session: SessionModel) { const owner = session.ownership.capture() const target = { sessionID, endpoint: server.conn.http } let registration: BrowserPaneRegistration | undefined + let retry: ReturnType | undefined + let attempts = 0 const register = () => { if (registration) return registration = pane.register(target, (event) => - owner.run(() => (event.type === "open" ? open() : setState({ browser: event.state, error: event.error }))), + owner.run(() => { + if (event.type === "open") return open() + // The desktop dropped the attachment (server restart, attach race). + // Re-register so the agent's browser tool comes back without a reload. + if (event.error === "browser.pane.registration.closed") { + registration?.close() + registration = undefined + setState({ registration: undefined, browser: null, error: undefined }) + retry = setTimeout(register, Math.min(30_000, 1_000 * 2 ** attempts++)) + return + } + setState({ browser: event.state, error: event.error }) + }), ) setState({ registration, browser: null, error: undefined }) } @@ -56,6 +70,7 @@ export function createSessionBrowser(session: SessionModel) { if (!session.shared.data.session.creating(sessionID)) register() onCleanup(() => { unsubscribe() + clearTimeout(retry) registration?.close() }) }) diff --git a/packages/app/src/session/files/session-side-panel.tsx b/packages/app/src/session/files/session-side-panel.tsx index 72d51776938..795676df03b 100644 --- a/packages/app/src/session/files/session-side-panel.tsx +++ b/packages/app/src/session/files/session-side-panel.tsx @@ -442,6 +442,9 @@ export function SessionSidePanel(props: { variant="ghost-muted" size="large" aria-label={language.t("session.tab.add")} + // The tablist redirects focus entering it to the selected + // tab, which counts as focus-outside and closes the menu. + onPointerDown={(event: PointerEvent) => event.preventDefault()} /> diff --git a/packages/desktop/src/renderer/platform/index.ts b/packages/desktop/src/renderer/platform/index.ts index a76bfbf2c33..9b01958ec17 100644 --- a/packages/desktop/src/renderer/platform/index.ts +++ b/packages/desktop/src/renderer/platform/index.ts @@ -39,6 +39,8 @@ export function createDesktopPlatform( if (!closed && value.bindingID === bindingID) onEvent(value.event) }) const ready = api.browserPane.request({ type: "register", bindingID, target }) + // Failures reach the owner through the closed-state event; keep the bare promise handled. + void ready.catch(() => undefined) return { setLayout(layout) { if (!closed)