Skip to content

Commit 445e896

Browse files
m-abboudclaude
andauthored
M1: Browser use in the platform-hosted virtual desktop (#42624)
* feat(gateway): proxy /v1/desktop/stream to the runtime and open it through velay (#41949) Claude-Session: https://claude.ai/code/session_012YDF6hHcX7aPZsc6TuByhy Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> * feat(web): interactive pod desktop panel over noVNC (#41951) Claude-Session: https://claude.ai/code/session_012YDF6hHcX7aPZsc6TuByhy Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> * feat(assistant): on-demand pod desktop session streamed over /v1/desktop/stream (#41952) * feat(assistant): on-demand pod desktop session streamed over /v1/desktop/stream Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012YDF6hHcX7aPZsc6TuByhy * chore(feature-flags): align pod-desktop registry wording with the web client entry Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012YDF6hHcX7aPZsc6TuByhy --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> * fix(gateway): desktop stream dropped-frame handling, shared guardian-stream tests, comment trims (#41957) Claude-Session: https://claude.ai/code/session_012YDF6hHcX7aPZsc6TuByhy Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> * fix(assistant): pod desktop startup rollback, tigervncconfig, baked Chromium, 4xxx close codes (#41966) Claude-Session: https://claude.ai/code/session_012YDF6hHcX7aPZsc6TuByhy Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> * fix(web): pod desktop clipboard leak, 4xxx close codes, lazy noVNC, shared gateway WS resolver (#41960) * fix(web): pod desktop clipboard leak, 4xxx close codes, lazy noVNC, shared gateway WS resolver Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012YDF6hHcX7aPZsc6TuByhy * fix(web): drop stray generated-client symlink from the commit Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012YDF6hHcX7aPZsc6TuByhy * fix(web): spell out desktop end-reason keys for the catalog-usage guard Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012YDF6hHcX7aPZsc6TuByhy --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> * fix(assistant): pod desktop retry waits for teardown, module-level shutdown latch, image and env hardening (#41985) Claude-Session: https://claude.ai/code/session_012YDF6hHcX7aPZsc6TuByhy Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> * chore(web): drop dead gateway WS URL builders and trim desktop stream comments (#41983) Claude-Session: https://claude.ai/code/session_012YDF6hHcX7aPZsc6TuByhy Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> * test(gateway): adopt the shared runtime-stream test harness in every stream suite (#41982) Claude-Session: https://claude.ai/code/session_012YDF6hHcX7aPZsc6TuByhy Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> * fix(web): let Escape reach the pod desktop instead of closing the modal (#41992) Claude-Session: https://claude.ai/code/session_012YDF6hHcX7aPZsc6TuByhy Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> * fix(assistant): pod desktop waits out SIGKILL before retry, bridge always releases on start failure (#41993) Claude-Session: https://claude.ai/code/session_012YDF6hHcX7aPZsc6TuByhy Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> * refactor: rename the pod-desktop flag to assistant-desktop (#42071) The flag is not in LaunchDarkly yet, so this is a pure rename with no migration or compatibility shim. - registry id/key `pod-desktop` -> `assistant-desktop`, label `Pod Desktop` -> `Assistant Desktop`, bundled copies resynced - `isPodDesktopEnabled` -> `isAssistantDesktopEnabled` and the flag constant in `assistant/src/desktop/desktop-feature.ts` - web selector `useAssistantFeatureFlagStore.use.podDesktop()` -> `.assistantDesktop()` - `PodDesktopAffordance` -> `AssistantDesktopAffordance`, files renamed to `assistant-desktop-affordance.tsx` / `.test.tsx` - i18n `podDesktop.*` -> `assistantDesktop.*` in all five locale catalogs and in the desktop-panel key map, keys still spelled out for the catalog guard - ARCHITECTURE.md section "Pod Desktop Stream" -> "Assistant Desktop Stream", plus the prose that carried the old name The route `/v1/desktop/stream`, the `assistant/src/desktop/` directory and the web `domains/chat/desktop/` directory are unchanged. Claude-Session: https://claude.ai/code/session_012YDF6hHcX7aPZsc6TuByhy Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(assistant): add a tint2 taskbar to the pod desktop (#42121) openbox alone draws no panel, so the streamed desktop had no taskbar, window list or clock. Launch tint2 with its stock config after the window manager, preflight the binary with the other three, and treat a panel exit as non-fatal: the desktop keeps running, and teardown still kills it. Claude-Session: https://claude.ai/code/session_012YDF6hHcX7aPZsc6TuByhy Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(assistant): dock-style desktop taskbar with pinned apps, wider viewer (#42124) * feat(assistant): dock-style desktop taskbar with pinned apps, wider viewer Generate a tint2rc at desktop start instead of running tint2 stock: a bottom-centered floating dock with rounded translucent chrome, icon-only launchers and window buttons, and generated .desktop launchers for Playwright's Chromium and xterm. Widen the viewer modal to min(96vw, 1600px) and shrink its header to a 36px band. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012YDF6hHcX7aPZsc6TuByhy * feat(assistant): run a compositor so the dock renders rounded tint2 only gets an ARGB visual when a compositor is already running, so the rounded translucent dock config rendered as an opaque square strip. Spawn xcompmgr right after openbox and before the dock, as a cosmetic child whose spawn failure and exit are logged and left alone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012YDF6hHcX7aPZsc6TuByhy --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(web): add a third-party licence notice for noVNC (#42142) noVNC is MPL-2.0, which asks that recipients of the executable form be told the licence and where to get the source. The bundler preserves its copyright headers but nothing carried the licence text or a durable source link. Adds a notice served from the web build root, so it travels with the bundle rather than living only in the repo. Records the component, version, copyright, licence, the pinned v1.7.0 source and npm tarball, and an explicit "not modified by Vellum", followed by noVNC's own notice (which says which of its files are MPL and which are more permissive) and the full MPL-2.0 text. The macOS and Windows clients package this same web build so they carry the file; iOS and Android load the hosted web app, so they reach it over the network. Claude-Session: https://claude.ai/code/session_012YDF6hHcX7aPZsc6TuByhy Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: scope the drop-close to the desktop stream and pin the runtime playwright (#42169) The shared frame pump behind /v1/stt/stream, /v1/watch/stream and /v1/desktop/stream closed both sides with 1011 whenever a downstream send returned 0. That teardown is desktop-specific: RFB is an ordered byte stream with no resync, so a lost frame corrupts the framebuffer for good, while the two JSON routes carry self-contained lifecycle and transcript frames. - `closeOnDroppedFrame` is now an opt-in on the handler factory, taken only by the desktop stream. stt and watch log and carry on, at debug rather than warn: the usual trigger is a runtime frame landing just after the browser socket closed, which is benign and once per session. - Bun's send returns a byte count, so an empty frame also returns 0. A drop now requires a non-empty payload. The image bakes Chromium from `/app/assistant`'s pinned playwright, but the runtime installed a floating one into the external dir. A different playwright looks for a different browser build id, so the bake bought a pod nothing and the first browser call or PDF export re-downloaded Chromium. The runtime install now asks for the version `assistant/package.json` pins, and replaces a copy left at any other version. Also restores the dropped live-voice assertion that a resolve without a conversationId produces a URL with no conversationId parameter. Claude-Session: https://claude.ai/code/session_012YDF6hHcX7aPZsc6TuByhy Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(desktop): install components on demand and launch Google Chrome * fix(desktop): keep failed setup retryable after partial installation * fix(gateway): register explicit desktop setup route paths * fix(desktop): use system apt and load the viewer after setup * fix(desktop): honor custom CA bundles during installation * fix(desktop): install components for direct stream clients * feat: add gated assistant desktop computer use * fix: preserve existing desktop VNC override capabilities * fix: validate drag destinations against desktop observations * fix: address desktop control lifecycle review feedback * Regenerate web API clients when development schemas change * Use the desktop feature flag for skill discovery and control * Decode DirectColor desktop screenshots with channel maps * Decode packed 24-bit desktop screenshot pixels * Smooth assistant desktop pointer movement * Use the assistant avatar as the streamed desktop wallpaper (#42549) * Add assistant avatar wallpaper to streamed desktop * Queue wallpaper refreshes during active avatar renders * Add raised assistant OS lettering to desktop wallpaper * fix(desktop): hide Chrome command-line warning through managed policy (#42562) * Fix streamed desktop app dock behavior (#42561) * fix(desktop): unify app launchers and running windows in the dock * fix(desktop): recover dock initialization and retain app cleanup * Polish streamed desktop window decorations (#42570) * Polish streamed desktop window decorations * Add avatar accents and preserve Openbox include paths * Use a subtle avatar gradient in desktop title bars * Add GNOME Mines to the streamed Linux desktop (#42576) * feat(desktop): include GNOME Mines in on-demand desktop setup * fix(desktop): add Mines to existing dock layouts * Use the browser CLI for streamed desktop Chrome over CDP * Release failed cleanup ownership when the desktop exits * Repair inherited companion markup and refresh the skill catalog * Render the desktop pointer without HTML injection * Refresh skill catalog after merging the feature branch * Follow current-turn cancellation when reusing desktop control * Queue API client generation across schema changes * Invalidate Vite caches after API client generation * Refresh skill catalog after merging main * Sync catalog metadata for the latest main merge * Direct desktop captures through the browser CLI * Preserve normal browser routing when desktop control ends * Separate desktop ownership from native computer actions * Keep M1 browser use independent of native computer control * Consolidate desktop browser guidance in CLI help * Reconnect desktop browser control to Chrome reopened from the dock * Refresh closed desktop CDP connections through input cleanup * Select browser defaults for web and native desktop clients * Preserve existing native browser selection * Rename virtual desktop and restrict it to platform assistants * Keep virtual desktop cursor visible across navigation and dialogs * Install virtual desktop automatically on first browser or viewer use * Keep virtual desktop cursor above keyboard-opened dialogs * Complete first browser action after virtual desktop setup * Align browser routing guidance with automatic first-use setup * Recover virtual browser profiles after container replacement * Trim repeated virtual desktop browser guidance * Restore M1 to bb9d565 before browser recovery changes * Keep computer observation hooks in M2 * Show virtual desktop controls only in the modal header * Center Desktop headers and shrink control buttons * Remove explicit desktop handoff and allow direct viewer input * Align desktop setup documentation with handoff removal * Remove obsolete handoff instructions from browser help * Fix IPC disconnect cleanup and remove remaining desktop handoff routes --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 5123707 commit 445e896

67 files changed

Lines changed: 3715 additions & 371 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

ARCHITECTURE.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -810,15 +810,15 @@ graph LR
810810

811811
## Assistant Desktop Stream
812812

813-
A containerized assistant can serve an interactive desktop on demand. The setup-capable modal installs desktop-only system packages and Google Chrome after the guardian clicks **Install desktop**. Authenticated direct stream requests also start or join the same background setup for clients without the setup UI. A client that times out during installation can reconnect after setup finishes; a disconnected viewer does not start a desktop process tree. `GET /v1/desktop/setup` checks readiness without installing anything; `POST` starts one shared background installation. Both flat and assistant-scoped gateway paths require guardian authentication and proxy to a gateway-service-only runtime route. `assistant:self:desktop` sync invalidations refresh setup status as installation progresses, and a reopened modal or reconnected event stream refetches it. Older assistants returning 404 retain the direct streaming flow.
813+
An enabled platform-hosted assistant can serve an interactive desktop on demand. Opening the virtual desktop picture-in-picture view automatically starts installation of desktop-only system packages and Google Chrome. The first eligible browser command waits for the same installation job and continues its original action once ready. Setup progress uses the same sync invalidations as the viewer. Cancellation or feature revocation prevents a delayed action. Browser CLI requests opt into IPC cancellation on disconnect so timed-out commands cannot act after setup finishes. Authenticated direct stream requests also start or join the same background setup for clients without the setup UI. A client that times out during installation can reconnect after setup finishes; a disconnected viewer does not start a desktop process tree. `GET /v1/desktop/setup` checks readiness without installing anything; `POST` starts one shared background installation. Both flat and assistant-scoped gateway paths require guardian authentication and proxy to a gateway-service-only runtime route. `assistant:self:desktop` sync invalidations refresh setup status as installation progresses, and a reopened modal or reconnected event stream refetches it. Older assistants returning 404 retain the direct streaming flow.
814814

815-
`desktop-dependencies.ts` installs the X server, window manager, dock, compositor, clipboard bridge, terminal, wallpaper setter (`feh`), fonts and Chrome libraries through image-root `/usr/bin/apt-get` with `--no-upgrade` and `--no-remove`. The installer uses only system command paths, bypassing Kata persistent-apt wrappers so binaries, X assets and shared libraries live in the same filesystem. Packages must be installed again when a Kata save or container recreation discards that root; Chrome and its profile remain in persistent storage. Google Chrome is an exact-version, SHA-256-verified download for Linux x64 or ARM64, extracted under the assistant's internal external-dependency directory. Extraction does not run Chrome package scripts, register its repository or change the system's default browser. Successful setup is recorded only after Chrome runs; readiness also checks the desktop binaries and X fonts so a recreated container offers setup again. Configured `NODE_EXTRA_CA_CERTS` are combined with the system CA bundle for apt and Chrome downloads without changing system trust. Installation errors remain retryable. The base image carries no desktop-only packages or baked browser; existing browser-tool and PDF installations retain their own Playwright behavior.
815+
`desktop-dependencies.ts` installs the X server, window manager, dock, compositor, clipboard bridge, terminal, wallpaper setter (`feh`), fonts and Chrome libraries through image-root `/usr/bin/apt-get` with `--no-upgrade` and `--no-remove`. The installer uses only system command paths, bypassing Kata persistent-apt wrappers so binaries, X assets and shared libraries live in the same filesystem. Packages must be installed again when a Kata save or container recreation discards that root; Chrome and its profile remain in persistent storage. Google Chrome is an exact-version, SHA-256-verified download for Linux x64 or ARM64, extracted under the assistant's internal external-dependency directory. Extraction does not run Chrome package scripts, register its repository or change the system's default browser. Successful setup is recorded only after Chrome runs; readiness also checks the desktop binaries and X fonts so a recreated container installs missing components on first use. Configured `NODE_EXTRA_CA_CERTS` are combined with the system CA bundle for apt and Chrome downloads without changing system trust. Installation errors remain retryable. The base image carries no desktop-only packages or baked browser; existing browser-tool and PDF installations retain their own Playwright behavior.
816816

817-
`DesktopSessionManager` owns `Xtigervnc` on display `:99` with VNC on `localhost:5999`, `openbox`, `xcompmgr`, `plank`, `tigervncconfig` and Google Chrome. A Python standard-library helper supervises Openbox and handles native title-bar move requests by restoring maximized windows before continuing the drag. The helper uses the installed X11 library and shares the window manager process group for shutdown. Chrome starts directly, without Playwright or automation switches, using the existing `data/desktop-profile` directory. The dock configuration and launcher paths remain under `data/desktop-panel`.
817+
`DesktopSessionManager` owns `Xtigervnc` on display `:99` with VNC on `localhost:5999`, `openbox`, `xcompmgr`, `plank`, `tigervncconfig` and Google Chrome. A Python standard-library helper supervises Openbox and handles native title-bar move requests by restoring maximized windows before continuing the drag. The helper uses the installed X11 library and shares the window manager process group for shutdown. Chrome starts directly with a loopback-only CDP port, using the existing `data/desktop-profile` directory. The assistant verifies that the listener belongs to its managed Chrome process before connecting. The dock configuration and launcher paths remain under `data/desktop-panel`.
818818

819819
Openbox loads a generated `data/desktop-panel/openbox.xml` with one workspace and no workspace-switching bindings or menus. The generator adapts the existing user config or the installed system config, preserving themes, window controls, shortcuts and application menus in managed copies. Nested XML includes are adapted into managed copies with their original lookup bases and XPointer selections preserved. Source files remain unchanged. If a custom XML configuration cannot be adapted, Openbox loads the existing source config and a warning is logged, preserving desktop availability. Restored windows are assigned to the sole workspace, and session-manager restoration is disabled. Each process tree owns a fresh X display; viewer reconnects retain its existing windows. The config is regenerated at desktop start, so existing installations adopt it without changing browser profiles or dock preferences.
820820

821-
Children receive only an allowlisted environment. One viewer holds the slot at a time; the tree lingers five minutes after disconnect. Chrome launches once when the desktop starts. Closing or crashing Chrome leaves the desktop running, and viewer reconnects keep it closed; the dock launcher can reopen it. Required child failures tear down the tree, while cosmetic dock/compositor failures leave the desktop running. Dock startup failures and exits receive up to three restart attempts per desktop session, one second apart. Shutdown uses SIGTERM followed by SIGKILL after a two-second grace, and a subsequent start waits for teardown. The `assistant-desktop` flag and `IS_CONTAINERIZED` gate both setup and streaming. The stream rechecks the gate after asynchronous setup and before forwarding either direction of traffic; revocation closes the viewer with `4008` and releases its slot. The companion platform PR adds authenticated desktop routing through velay; it does not change pod memory or shared-memory provisioning.
821+
Children receive only an allowlisted environment. One viewer holds the slot at a time; the tree lingers five minutes after disconnect. Chrome launches once when the desktop starts. Closing or crashing Chrome leaves the desktop running, and viewer reconnects keep it closed; the dock launcher or an explicit browser CLI action can reopen it. Required child failures tear down the tree, while cosmetic dock/compositor failures leave the desktop running. Dock startup failures and exits receive up to three restart attempts per desktop session, one second apart. Shutdown uses SIGTERM followed by SIGKILL after a two-second grace, and a subsequent start waits for teardown. The `assistant-desktop` flag, `IS_PLATFORM` and `IS_CONTAINERIZED` gate both setup and streaming. Self-hosted Docker assistants cannot use the virtual desktop. The stream rechecks the gate after asynchronous setup and before forwarding either direction of traffic; revocation closes the viewer with `4008` and releases its slot. The companion platform PR adds authenticated desktop routing through velay; it does not change pod memory or shared-memory provisioning.
822822

823823
Plank runs under `dbus-run-session` with private XDG configuration/data paths and the keyfile settings backend; its BAMF matcher shares that session bus and process group. The manager retains each exited dock group during recovery so its applications keep running, then clears all surviving groups at teardown. Shutdown cancels pending dock restarts. Chrome and Terminal launchers use their real X11 identities, with Chrome's official packaged icon and profile-specific identity. Pinned launchers represent running applications, with Plank providing focus, minimize/restore, window selection, and explicit new-window gestures. Default pins and preferences are published atomically on first use; subsequent starts refresh managed launcher paths while preserving user customization. An installation missing Plank or BAMF requests on-demand setup.
824824

@@ -828,6 +828,14 @@ Before launching Chrome or exposing its dock launcher, the Linux container sessi
828828

829829
**Transport.** `/v1/desktop/stream` is a pure RFB byte pipe: after the upgrade, every frame in both directions is binary and `DesktopStreamBridge` (`desktop-stream-bridge.ts`) pumps it to and from the VNC port, buffering client bytes that arrive before that socket is up. Nothing is signaled in-band; outcomes are close codes in the application range so they can neither collide with velay's own `1013` nor be remapped by the gateway's velay bridge. The manager decides them and the bridge only relays (`DesktopLoss`, through the viewer-slot result, `onDesktopLost`, or the `DesktopStartError` a start rejects with): `4008` desktop disabled or unsupported on this daemon, `4013` another viewer holds the slot, `4011` the desktop failed to start, died under the viewer, or the viewer fell too far behind (a dropped `ws.send`), and the standard `1001` when the runtime is shutting down, whether the socket arrived after shutdown began or a live viewer is cut off by it. On the managed path velay's bridge carries the runtime's `1001` as `4001` and the gateway's `1011` as `4011`, both of which the panel treats as retryable endings. The daemon upgrade is gated exactly as `/v1/watch/stream` (private-network peer and origin, gateway service token, one shared `upgradeRuntimeStream` path); the feature gate runs after the upgrade because the gateway relays close codes, not HTTP statuses, to the browser. VNC needs no password: only same-pod processes can reach the loopback port, and the authenticated upgrade is the only bridge to it.
830830

831+
### Virtual desktop browser use
832+
833+
`assistant browser --virtual-desktop` controls the managed Chrome window. Web conversations select it automatically when available; native desktop clients retain their existing browser behavior and require an explicit choice to use streamed Chrome. The shared browser target resolver honors explicit modes, client targets and existing personal-browser sessions. It distinguishes Electron from plain web using the frozen turn client OS while retaining the original transport identity. Usage guidance lives in `assistant browser --help`. Platform hosting, the default-off `assistant-desktop` flag, completed automatic installation and identified guardian conversation gate automation. No connected host desktop client is needed. Existing host computer-use tools keep their connected-client routing.
834+
835+
`DesktopAutomationLease` serializes browser automation and binds ownership to one conversation and actor. The browser CLI borrows a scoped direct CDP client and dispatches through the existing browser operation handlers. Snapshot references use a separate desktop namespace and are invalidated on navigation, tab changes and release. A page overlay animates the CDP pointer in the stream. Page screenshots come directly from Chrome as color JPEGs. Native application control and whole-desktop screenshots are outside this browser interface. See [desktop browser CLI](assistant/docs/desktop-browser-cli.md).
836+
837+
An automation slot keeps the desktop alive independently of the viewer. The picture-in-picture preview is view-only; opening the desktop allows direct mouse, keyboard and clipboard input without a handoff step. Viewer interaction does not pause browser automation. Cancellation, desktop loss, inactivity and `detach` release the automation slot and clean up held browser input. No control state or screenshots are persisted outside normal tool history.
838+
831839
## Screen Annotation
832840

833841
The assistant points at things on the screen the user is sharing with a call, so they can go and do the thing themselves. It is the opposite errand from computer use and shares none of its actions: nothing here clicks, types or takes the mouse. The bundled `screen-annotation` skill (`assistant/src/config/bundled-skills/screen-annotation/`) offers two tools, `screen_point_at` and `screen_clear_marks`, and a request replaces whatever is currently drawn. Clearing is its own tool because it is a thing the model decides to do rather than an argument shape it has to remember; on the wire it is the same request carrying no marks.

0 commit comments

Comments
 (0)