fix(desktop): bound browser registration and stop retrying unsupported servers

This commit is contained in:
LukeParkerDev 2026-09-02 19:45:20 +10:00
parent ad5a5ef5ad
commit 00d0413bb1
2 changed files with 23 additions and 6 deletions

View file

@ -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 })
}),
)

View file

@ -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?.()