Skip to content

Releases

0.3.0 — renamed to dsh-embedded-browser, tools published lazily, an on-demand Sidebar tab

Choose a tag to compare

@imroc imroc released this 21 Sep 09:54

This release renames the plugin. Updating from dsh-browser-panel means changing every name it used — and moving a directory of data.

What breaks

All at once, on purpose, so that the package, the tools and the routes finally agree with each other:

0.2.0 0.3.0
package dsh-browser-panel dsh-embedded-browser
tools browser_panel_* browser_embedded_*
routes /api/dsh-browser-panel/* /api/dsh-embedded-browser/*
profile $DSH_HOME/browser-panel/profile $DSH_HOME/embedded-browser/profile

The profile directory is data, not a string. Move it before the first start of 0.3.0, or the browser comes up with an empty profile and every site asks you to log in again:

# no Chrome may be running on it
mv ~/.dsh/browser-panel ~/.dsh/embedded-browser

Alternatively point profileDir at the old directory and keep it where it is.

Two surfaces also disappear: the per-session conversation.view tab and the host-wide overview panel are gone, replaced by an on-demand tab in the right Sidebar.

What changed, and why

The ten tools are published lazily. A tool schema is paid for on every request whether or not the turn touches a browser; a skill's catalog entry is two lines, and its body is read only when the model decides to read it. browser_embedded_* now appears only after the entry skill browser-use has been invoked — a successful skill call, a /browser-use gesture, or a past invocation found in a live session's log when the plugin loads. That third path is the one that matters: a gate that only listens for future events stays shut forever after a plugin reload, because the invocation that opened it is in the past (upstream Tencent/BrowserSkill#269 is exactly that failure, and it is why the reference implementation turned its own gate off). lazyTools: false restores the old behaviour.

Measured on the development host, read from the session log's request/header — the tool list as the model actually received it:

tools schema bytes
0.2.0, registered at load 73 72,541
0.3.0, gate closed 63 67,958
0.3.0, after browser-use 73 72,732

A session that never browses therefore sends about 4,774 bytes (~1.2k tokens) less on every request.

The UI is an on-demand right-Sidebar tab. One registered tab type plus a watcher that opens it when the session on screen gets a browser, or when a new hand-over request arrives. Nothing is pinned per session, so a session that never browses shows nothing at all. The tab opens its stream from tab.visible rather than from mounting: a docked body stays mounted while its tab is inactive, and a hidden tab holding a socket would keep stealing the browser's foreground and spending bandwidth nobody is looking at.

Client services are now awaited, not sampled. The Sidebar tab silently failed to register in the first build of 0.3.0: ctx.get('sidebarRightTabs') is undefined during apply(), because the client half really is activated before the Sidebar provides its services (the boot graph's order is not the activation order). Every Sidebar-facing surface was skipped, the host stayed perfectly healthy, and the only witness was one line in the browser console. ctx.inject waits for the declaration instead — the rule the host half already followed for webServer/connection.

Upgrading

  1. Move the profile directory if you want to keep your logins (above).
  2. In your profile, replace the dsh-browser-panel bundle/dependency entry with dsh-embedded-browser, then re-install.
  3. Install the browser-use skill, or set lazyTools: false. Without either, the tools are never published.
  4. Rename any tool call you make by hand: browser_panel_* → browser_embedded_*.

How this was verified

  • npm test: 22 checks for the lazy gate (a fake context; no host, no browser — including the composition-order and reload cases) and 43 for the core (real Chrome).
  • The gate end to end on a live host, read from the session log's tool list: 73 → 63 tools with the gate closed, 73 again after invoking the skill, with all ten browser_embedded_* names present.
  • Routes: /api/dsh-embedded-browser/health answers 200 with the Web UI's own authentication cookie and 401 without it (the gateway fence, which is why the old prefix answers 404 only when authenticated).
  • The browser itself, on both channels: the BrowserSkill plugin's browser_* tools against a real user browser, and this plugin's browser_embedded_* tools against the Chrome inside the container — navigate, read back page structure, and the right-Sidebar tab observed opening by itself.
  • The rename's data step: the 0.2.0 profile directory was moved, and the logins came with it.

0.2.0 — one browser tab per session

Choose a tag to compare

@imroc imroc released this 14 Sep 06:13

Breaking: every DSH session now gets its own tab in the shared browser. 0.1.x owned a single page for the whole host, so two conversations trampled each other's page.

What changed

  • One Chrome, one tab per session. A session's tab is created on its first browser_panel_* call (or from the panel's open button) and is activated once at creation — a Chrome target that was never activated silently discards every injected input event for the rest of its life.
  • The profile stays shared, on purpose. Sessions are isolated in page state, not in identity: log in once (SSO, QR, 2FA, CAPTCHA) and every session is authenticated.
  • Tools follow the caller. Handlers take the session from their execution context, so tool names and parameters are unchanged, and one session can never steer another's page. A call without an owning session is an error rather than a silent fallback to a shared page.
  • The panel lives in the session page. A fixed 浏览器 / Browser tab in the session's view strip renders that session's browser; the sidebar entry became a host-wide overview (rows per session tab, pending take-over badge, per-row teardown). The root-scoped canvas panel is gone.
  • Streaming follows the human. Chrome streams only the active tab, so the session whose panel is actually being watched owns the real screencast and every other attached session is served by polled captures (~7 fps, optimizeForSpeed). A fresh panel is always seeded a frame.
  • Take-over is per session. Two sessions can wait on a person at the same time, and a request is delivered only to the panel of its own session.
  • Closing is explicit. browser_panel_close, the panel's 结束并清理, or the session being disposed close that session's tab. Unmounting the panel hands the foreground back and never closes anything.
  • Idle reaping is lossless. After idleShutdownMinutes (default 10, 0 disables) the browser stops; the next call reopens each session at its last URL, already authenticated.

Upgrading

No configuration change is required. Two behaviour notes: browser_panel_close now closes the calling session's tab instead of stopping the whole browser (there is no stop-everything tool any more), and browser_panel_navigate's newTab replaces this session's tab. A new pollFrameMs (default 140) tunes the fallback stream for panels that do not hold the foreground.

Verification

43-check standalone smoke suite (node test/smoke.mjs) covering per-session isolation and real input delivery — two sessions get two tabs, tabs do not steer each other, click-then-type really lands in the field, the focus gate is asserted, panel input reaches its own session only, the watched session owns the screencast while the other is polled, a panel reconnect survives, and a login survives a browser restart. Plus a live install check: tools bound to the calling session, a logged-in page coming back after the upgrade, and /open + /close touching exactly one session.