feat(panel,host,desktop,web): watch and take over an agent's cloud Browser tab

Merged
DA
5d ago
+2869-33

PR F of the "Browser agent tab automation setup" rollout: the panel side of the cloud browser. A task's agent Browser tabs now show in the side panel, on the desktop and on the web. Each one plays live on a canvas, and a member can take it over. It sits behind Settings › Experimental › Browser tab.

What's in it

A cloud kind of Browser tab (CloudBrowserStripTab in @luke/ui/tab-strip-model)

  • The panel reads the task's tabs from the service (agentBrowserTabs.list, through a new tasks.browserTabs act, gateway method and host read). Every reader of the task shares that one read.
  • The task record's Neon Realtime read doesn't carry the tabs, so the list is read again every 5 s while the task is open.
  • Each tab is named by its page title, with the address on hover. Its detail says whose browser it's in: the agent's name, or Luke.
  • Closing a tab hides it in this window only. A chosen tab stays chosen while the list is being read again.
  • It's offered behind the Browser tab experiment on every platform, the web included, because it uses none of the Mac tab's acts.

The live view (packages/panel/src/tasks/cloud-browser-live.ts, cloud-browser-pane.tsx)

  • Frames are drawn on a canvas that keeps the page's shape inside the pane. The canvas size is the viewport every point is sent in.
  • viewport is sent when the socket opens, on every resize, and once a minute after that (Schedule.spaced).
  • After a drop, the view reconnects with an exponential backoff from 1 s up to 30 s (Schedule). The backoff starts over once a socket has been heard on.
  • not-opened is asked again every 5 s. A socket closed as idle or gone stays closed until the pane is shown again; an idle one also offers Watch again.
  • Until the first frame, it says "Waiting for the page".
  • The pane is ph-no-capture, and session replay already doesn't record canvases. session-replay.ts and PRIVACY.md now name the live view. Frames, address and title are drawn and dropped; none of them goes into app state, a trace or a count.

Takeover in the tab's bar

  • Take over turns into Hand back while you drive. The bar says "You're driving", " is driving", or " is using this tab" when the agent acted in the last 15 s.
  • While you drive, mouse, wheel, keys and paste go over the wire in viewport pixels.
  • Command chords the window answers stay with the window (chordIsWindows), as on the Mac Browser tab. ⌘A/C/X/Z go to the page as Control, because the page runs on Linux. ⌘V arrives as a paste.

The socket's two doors (AppPlatform.openBrowserLive)

  • Desktop: the host opens the relay socket under the account's bearer, using the Terminal tab's socket opener and its bearer-renew step (now shared as relay-bearer.ts). Main (browser-live-acts.ts) posts a MessagePort to the asking panel only, and the preload hands it into the page as it does a terminal's port. Main refuses the attach from anything but a panel, or while the experiment is off.
  • Web: web-browser-live.ts opens a plain same-origin WebSocket from the page, carrying the session cookie.
  • The reconnect and viewport logic lives in packages/panel, the same for both doors.

Screenshots

These are fixture runs with a staging patch that wasn't committed: a fake socket sends a synthetic sign-in page, and the list read returns two fixture tabs. Desktop is the Electron app under Xvfb; web is the web app's own page.

Desktop: Luke is using the tab · after Take over

Desktop, Luke using the tab Desktop, driving

Web: Luke is using the tab · after Take over

Web, Luke using the tab Web, driving

Tests

  • cloud-browser-live.test.ts (TestClock): a frame is drawn, and the viewport is sent on open and again after a minute; take over and hand back are sent, and input goes only while the member drives; a socket that drops reconnects after the backoff, and the backoff doubles when a socket never opens; an idle close stays closed.
  • cloud-browser-tab.test.ts (jsdom): the task's tabs are read only while the experiment is on; a listed tab stands in the strip, stays chosen while the list is being read, closes in this window, and gives way once it isn't listed; the pane waits, draws once a frame arrives, and takes over and hands back.
  • browser-live-acts.test.ts (desktop main): the port goes to the asking panel only; the service's frames reach that port only, and a close closes it; detach and the page going away let the view go; attach is refused with the experiment off or from a window that isn't a panel.
  • compose-terminals.test.ts (host): the socket opens under the bearer at the tab's path; sends made while it opens arrive first; the service's frames reach the panel by connection, with the close last; a refused socket closes as 1006; a view that was let go says nothing more.
  • tasks-procedures.test.ts: the tabs act answers a panel, and an unanswered read is refused.
  • web-browser-live.test.ts: the web door opens on the page's own origin, holds sends until the socket is open, and passes on the service's frames and the close.

Screenshots

These were taken with a throwaway staging patch that isn't committed: two fixture tabs, a fake socket sending a synthetic sign-in page, and the experiment on. Desktop is the fixture run (--fixture smoke --profile tasks-transcript) under Xvfb; web is the web app on Vite with the same fixture profile.

Watching (Luke acted lately)Taken over
DesktopDesktop: the cloud Browser tab beside the plan, Luke is using this tab, Take overDesktop: You're driving, Hand back
WebWeb: the cloud Browser tab beside the planWeb: You're driving, Hand back

Not in it

  • D's connectRestoring switch in the relay: #4318 hasn't merged yet.
  • The list isn't refreshed when a browser tool call lands in the transcript; the 5 s re-read covers it.
  • Locally, the sandbox ran out of memory on pnpm check:affected's service typecheck and discarded-effect steps (neither touched here). I typechecked host, panel, features, ui, desktop and web on their own, and lint passed. CI runs the rest.

Made with Luke


Devin Review