From 00d0413bb10c74af58bb80ae7599a6b15f4aef0f Mon Sep 17 00:00:00 2001 From: LukeParkerDev <10430890+Hona@users.noreply.github.com> Date: Wed, 2 Sep 2026 19:45:20 +1000 Subject: [PATCH] fix(desktop): bound browser registration and stop retrying unsupported servers --- packages/app/src/session/browser/model.ts | 7 ++++++- packages/desktop/src/main/browser-pane.ts | 22 +++++++++++++++++----- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/packages/app/src/session/browser/model.ts b/packages/app/src/session/browser/model.ts index 2022119633f..03c90fcd797 100644 --- a/packages/app/src/session/browser/model.ts +++ b/packages/app/src/session/browser/model.ts @@ -17,6 +17,8 @@ export function createSessionBrowser(session: SessionModel) { registration: undefined as BrowserPaneRegistration | undefined, browser: null as BrowserPaneState, error: undefined as string | undefined, + // The connected server has no browser plugin. + unsupported: false, }) const available = createMemo( () => @@ -25,7 +27,8 @@ export function createSessionBrowser(session: SessionModel) { settings.general.experimentalBrowser() && session.isDesktop() && !!session.identity.sessionID() && - !server.health?.incompatible, + !server.health?.incompatible && + !state.unsupported, ) const opened = () => state.registration !== undefined && session.layout.tabs().all().includes(SESSION_BROWSER_TAB) const open = () => { @@ -49,6 +52,7 @@ export function createSessionBrowser(session: SessionModel) { registration = pane.register(target, (event) => owner.run(() => { if (event.type === "open") return open() + if (event.error === "browser.pane.unsupported") return setState("unsupported", true) // 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") { @@ -58,6 +62,7 @@ export function createSessionBrowser(session: SessionModel) { retry = setTimeout(register, Math.min(30_000, 1_000 * 2 ** attempts++)) return } + if (event.state) attempts = 0 setState({ browser: event.state, error: event.error }) }), ) diff --git a/packages/desktop/src/main/browser-pane.ts b/packages/desktop/src/main/browser-pane.ts index 1cba61808e6..a94f7952a36 100644 --- a/packages/desktop/src/main/browser-pane.ts +++ b/packages/desktop/src/main/browser-pane.ts @@ -40,7 +40,9 @@ export function createBrowserPane() { registered: Promise.withResolvers(), requests: new Map(), } - const stop = () => close(entry) + // "unsupported" means the server has no browser plugin; the renderer stops retrying. + let reason: "browser.pane.unsupported" | undefined + const stop = () => close(entry, reason) const navigate = (event: Electron.Event<{ isMainFrame: boolean; isSameDocument: boolean }>) => { if (event.isMainFrame && !event.isSameDocument) stop() } @@ -134,11 +136,21 @@ export function createBrowserPane() { Stream.fromQueue(outbound).pipe(Stream.runForEach((send) => send)), Deferred.await(connected).pipe(Effect.andThen(rpc.attach(attachment, options))), ]) - }).pipe(Effect.scoped, Effect.ensuring(Effect.sync(stop))), + }).pipe( + Effect.scoped, + Effect.tapError((error) => + Effect.sync(() => { + const type = error instanceof Object && "type" in error ? error.type : undefined + if (type === "rpc.unavailable" || type === "rpc.method_not_found") reason = "browser.pane.unsupported" + }), + ), + Effect.ensuring(Effect.sync(stop)), + ), { signal: entry.abort.signal }, ) .catch(stop) - await entry.registered.promise + const timeout = setTimeout(stop, 15_000) + await entry.registered.promise.finally(() => clearTimeout(timeout)) if (entries.get(bindingID) !== entry) throw new Error("browser.pane.registration.closed") publishState(entry) }, @@ -183,10 +195,10 @@ export function createBrowserPane() { emitIpcEvent(entry.win.webContents, new BrowserPaneEvent({ bindingID: entry.bindingID, event })) } - function close(entry: Entry) { + function close(entry: Entry, reason = "browser.pane.registration.closed") { if (entries.get(entry.bindingID) !== entry) return entry.report = undefined - closePage(entry, "browser.pane.registration.closed") + closePage(entry, reason) entries.delete(entry.bindingID) entry.registered.reject(new Error("browser.pane.registration.closed")) entry.cleanup?.()