fix(app): keep the browser attachment and tab menu reliable

This commit is contained in:
LukeParkerDev 2026-09-02 19:33:15 +10:00
parent 5f6a75fa23
commit b676c4fe0e
3 changed files with 21 additions and 1 deletions

View file

@ -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<typeof setTimeout> | 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()
})
})

View file

@ -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()}
/>
<Menu.Portal>
<Menu.Content>

View file

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