browser_list answered focus: "main" from a literal. The tools that moved a
focus went with the eight-browser session on 2026-09-11 and nothing replaced
them, so the field had been a constant since. The interface believed it: it
draws a dot on that browser whose title reads "the agent is working here", so
with the helper open the dot sat on main while the agent typed into support,
and the screen marked aria-current was main whatever the agent was doing. The
one thing that dot exists to say was the one thing it could not say.
The fact was already inside Work.acting, which every command that touches a
page goes through and which knows the role of each one. It was thrown away.
Now it is kept, and focused() hands it out only when that browser is still
open, falling back through roles() so main wins whenever it is up and "" means
nothing is open. Opening a browser counts as working in it; a command that
FAILED still happened there, so the role is recorded once the browser has
answered for itself and before the action runs.
Where a command that names no browser lands did not move: it is main, it is a
constant of the two roles rather than a fact about this moment, and it is said
in note and in the tool description. The two were the same string until now,
which is exactly why one line used to serve both.
Three things went with it.
focused, per row, was id == focus with focus named two lines above it in the
same answer. limit was the constant 2 and nothing in this product ever read
it: the word does not appear in the interface at all, and note already says
how many of how many. Same reason running went in 0.54.0.
The stage stopped sorting the watched browser to the front. That meant
something while a session held up to eight browsers and the stage showed fewer
than all of them; it now shows two screens exactly when there are two
browsers, so the sort could only decide which of two equal cells sat on the
left - and with focus becoming a real fact it would have swapped the two panes
under the eye of whoever was watching, every time the agent moved between
them. The watched screen is marked, never moved.
And removing limit exposed something the route was doing. /live/browsers
smoothed an unreadable answer into an empty workspace with a 200, for a server
older than 0.18.0 that replied in prose - a server that cannot exist any more,
because the interface spawns the one it ships with out of this same package.
What the smoothing still did was make a failure look exactly like an empty
room, and with limit gone the fallback body became byte-identical to a genuine
"nothing is open". It is a 503 with the reason now, the same shape /live/frame
uses, and the page keeps the stage it already has instead of tearing every
screen down for one unanswered poll.
The wiki's sample of a client session was rebuilt by RUNNING the script it
publishes rather than by editing it: it still showed running: false, removed
in 0.54.0, and a fleet containing a browser that a freshly started server has
not had since open-first landed in 0.53.0. The description-size anchor caught
the twelve pages that publish the tool surface; nothing was watching the
sample, which is why it was three versions stale.
Suite 589 green. Eleven known-bad inputs, eleven killed: the role not
recorded, the focus handed out without asking whether it is still open,
opening not counting as working in it, the note reading the focus, the rows
carrying focused again, the unreadable answer smoothed into an empty
workspace, the watched screen sorted to the front, the dot on every screen,
the dot on none, a failed poll emptying the stage, and the address bar
answering some row when nobody is being watched. A twelfth was written and
reported a survivor: it changed the find inside addressOf while the guard
above it answered first, so the known-bad never reached the line it changed.