Skip to content

feat: stream Agent Manager browser previews with public HTTPS - #13804

Merged
marius-kilocode merged 14 commits into
mainfrom
browser-public-https-13618
Sep 25, 2026
Merged

marius-kilocode merged 14 commits into
mainfrom
browser-public-https-13618

Conversation

@marius-kilocode

@marius-kilocode marius-kilocode commented Sep 4, 2026 •

Copy link
Copy Markdown
Collaborator

What Problem This Solves

Agent Manager's browser is limited to HTTP loopback applications. Its iframe preview is separate from the Chromium page used for automation, so framing restrictions can prevent a page from appearing and the visible page is not necessarily the page being inspected. Local applications also need public CDN modules and other secure resources.

This implements the public HTTPS use case tracked in #13618. It deliberately changes the preview architecture instead of removing framing headers. It also expands resource access beyond that issue's original same-origin-only proposal: public HTTPS/WSS resources may load across origins, while new document origins require approval.

Why This Change Was Made

  • Render the same controlled Chromium page in Agent Manager through a high-resolution CDP stream. Keep frame delivery bounded, coalesce wheel input, and reject stale navigation, viewport, and session identities.
  • Preserve native CORS preflight decisions with browser-target Fetch interception. Unowned pages are rejected before their first HTTP request, including warm popups and directly created targets.
  • Keep the broker authenticated and loopback-only. Per-context proxies validate DNS answers, dial validated addresses, and prevent public pages from reaching private or loopback destinations. Certificate validation stays enabled.
  • Retain the existing Playwright integration and browser-installation model. No new browser installer or separate automation framework is added.

Architecture

The preview is not an iframe, and it does not use VS Code's integrated browser API. A headless Chrome process runs outside the webview; only encoded frames and input events cross the boundary. This is the same family of design as the older VS Code browser-preview extensions: the extension host owns the browser and enforces policy where connections are actually made, while the webview only paints pixels and forwards input.

flowchart LR
  subgraph HOST["VS Code extension host (Node.js)"]
    BRK["BrowserBroker<br/>authenticated loopback API"]
    STR["BrowserStream<br/>CDP screencast + input"]
    NET["BrowserNetwork + BrowserProxy<br/>request policy, DNS pinning"]
  end
  subgraph WEB["Agent Manager webview (SolidJS)"]
    VP["StreamViewport<br/>canvas + input capture"]
  end
  subgraph CHROME["Headless Chrome (separate OS process)"]
    CTX["BrowserContext per session<br/>one page + per-context proxy"]
  end
  CLI["CLI agent<br/>browser_open tool"]

  VP -- "postMessage: viewport / interact" --> BRK
  BRK -- "postMessage: frame (JPEG + identity)" --> VP
  STR -- "newCDPSession, Input.*" --> CTX
  STR -- "Page.startScreencast / ack" --> CTX
  NET -- "proxy + Fetch gate" --> CTX
  CLI -- "Bearer token over 127.0.0.1" --> BRK
Loading

One Chrome process is shared by the extension host, and each session gets its own isolated BrowserContext. Chrome sends JPEG frames over a CDP screencast; the webview decodes them onto a canvas and drops stale frames by identity. Pointer, keyboard, wheel, IME, and clipboard events travel back and are replayed with CDP input calls.

Policy is enforced at the connection boundary, not by inspecting page content:

flowchart TB
  subgraph CONTENT["Page content (untrusted)"]
    PUB["Public HTTPS document"]
    LOCAL["Approved localhost app"]
  end
  FG["Browser-target Fetch gate<br/>rejects unowned frames before first request"]
  PX["Per-context authenticated proxy<br/>validates DNS, dials pinned IP"]
  PUB --> FG
  LOCAL --> FG
  FG --> PX
  PX -->|public origins| EXT["Public HTTPS / WSS"]
  PX -->|approved origin only| LOOP["localhost:port"]
  PX -.->|denied| PRIV["Private / link-local / LAN"]
Loading

Local HTTP applications remain limited to their approved origin, public pages cannot reach private or loopback destinations, navigation to a new origin requires approval, and certificate validation stays with Chromium. While the agent sandbox is enabled, the agent cannot drive this browser at all, so the stream cannot become a network bypass; the manual Browser panel still works.

Key files: BrowserBroker, BrowserStream, BrowserNetwork, BrowserProxy, StreamViewport, and the browser_open tool.

User Impact

  • Open public HTTPS pages from the Browser panel and browser_open, alongside HTTP localhost applications. Bare public hostnames in the address bar use HTTPS.
  • Local HTTP/WS traffic and document navigation stay on the approved local origin. Local pages can load public HTTPS/WSS resources, including CDN-hosted Three.js modules. Public pages cannot access private or loopback destinations.
  • Use pointer, keyboard, IME, and plain-text clipboard input in the streamed page. Clipboard operations remain ordered and respect page handlers. Closing a stalled renderer no longer waits behind its pending input evaluation.
  • Go back and forward with toolbar buttons. The buttons follow the real page history and ignore the initial blank page.
  • Recover from a browser crash with Refresh. If Chromium exits, every open panel shows "The browser stopped unexpectedly. Refresh to start it again." and the next Refresh or open starts a new browser. A crashed page is replaced the same way.
  • See "Cannot connect to . Make sure the local server is running." when a local server is not running, instead of a misleading HTTP 403.
  • Open http://0.0.0.0:<port> URLs printed by dev servers. They open as http://localhost:<port>, because 0.0.0.0 is a listen address, not a destination, and localhost is the origin dev tools usually configure.
  • See first-use browser requirements and localized missing-browser recovery actions in 21 languages. Chrome is the default; a compatible installed Playwright browser is the alternative. The experimental flag remains off by default and workspace trust is still required.

Known limits:

  • Native Windows/Linux operation has not been exercised in this environment.
  • Headless Chromium does not throttle hidden pages. A hidden worktree's page keeps running (about 7-8% of a core for a small animated page) until its browser closes. Page freeze, window minimize, background tabs, and CPU throttling have no useful effect in headless mode.
  • Each local origin gets its own browser context, because the per-context proxy is locked to one local origin. Opening a different local port, or switching between local HTTP and public HTTPS, starts a fresh context without cookies. Public HTTPS pages share one context.
  • Native macOS select popups are not captured. Clipboard events are synthetic and support plain text, not full native rich-clipboard behavior.
  • The pinned Playwright 1.57/Chromium 143 combination has an upstream nested-frame locator/evaluation limitation after process swaps. The compatibility regression uses native DOM and HTTP assertions for that case; it does not claim the upstream locator problem is fixed.
  • This is not an OS-enforced network sandbox around Chromium. Public TLS preconnects can occur without an HTTP request. Corporate proxy/PAC-only networks are not supported.

Evidence

Latest changes (4ef8aeb55b), verified in an isolated VS Code instance with two Agent Manager worktrees on macOS:

  • Two worktrees keep separate cookies, storage, and input. Only the visible worktree streams; with the panel closed, zero frames are sent. Worktree switch to first frame: 11 ms. One visible stream runs at about 60 fps.
  • After Chromium was killed, both worktrees showed the new error and recovered with Refresh in one new Chrome process.
  • An agent-style browser_open with the panel closed streamed in 3 of 3 runs (0 of 3 before the fix). The cause was a Playwright screenshot restoring the viewport during a concurrent stream resize; screenshots now run in the stream queue.
  • Back and forward buttons enabled and disabled from the real navigation history and navigated correctly.
  • Browser test files: 316 passed, 0 failed. Extension lint, typecheck, and Knip passed. CLI sandbox/browser tests: 14 passed.

Fresh checks after updating the branch to 1536aef0fb:

  • Extension unit suite: 5,070 passed, 4 skipped, 0 failed, 26,667 assertions across 389 files. Skips cover a Windows-only case and opt-in external probes.
  • CLI sandbox/browser tests: 13 passed, 80 assertions. Destination classifier tests: 3 passed, 47 assertions.
  • Extension and CLI typechecks, extension/CLI lint, production build, Knip, formatting, Kilo marker guard, source-link extraction, and upstream annotation checks passed.
  • The local CLI build used the active Bun runtime after the pinned bunx bootstrap failed; the resulting binary passed its version, models, and sandbox-worker smoke tests.

During implementation, controlled real-Chromium regressions verified rejected preflights send no mutation, allowed preflights complete, private/loopback redirects and workers make no forbidden contact, and unowned popup/target requests are blocked. A focused run on both installed Chrome and pinned Chromium 143 passed 139 tests, with 3 opt-in external probes skipped.

Isolated VS Code checks verified the missing-browser settings/retry recovery flow, local CDN/WebGL loading, copy/cut followed by custom paste handling, and stream visibility behavior. A real Chromium page with an eight-second blocking copy handler closed in 14 ms and reopened in a fresh context. These are scoped macOS results, not cross-platform performance guarantees.

Streamed local fixture after copy/cut and a page-handled paste:

Streamed browser fixture with an empty source field and a page-handled paste result

Missing-browser recovery guidance:

Missing Playwright browser warning with Retry and Browser Settings actions

Manual verification: enable Browser Automation in a trusted workspace; open localhost and an approved HTTPS page; test clipboard cancellation/custom handling; close and reopen a busy page; and check the recovery actions when the selected browser is unavailable.

@kilo-code-bot

kilo-code-bot Bot commented Sep 4, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
packages/kilo-vscode/src/services/browser-automation/browser-broker.ts 863 With public HTTPS now supported, an HTTP >= 400 response from a public page is still reported as Local application returned HTTP ... and recorded as a navigation error.
Files Reviewed (incremental delta since df41127)
  • packages/kilo-vscode/src/services/browser-automation/browser-broker.ts - 1 issue
  • packages/kilo-vscode/src/services/browser-automation/browser-policy.ts
  • packages/kilo-vscode/src/services/browser-automation/browser-proxy.ts
  • packages/kilo-vscode/src/services/browser-automation/browser-stream.ts
  • packages/kilo-vscode/src/agent-manager/browser-message.ts
  • packages/kilo-vscode/src/agent-manager/project/state-gate.ts
  • packages/kilo-vscode/src/agent-manager/types.ts
  • packages/kilo-vscode/webview-ui/agent-manager/BrowserPanel.tsx
  • packages/kilo-vscode/webview-ui/browser/BrowserPanel.tsx
  • packages/kilo-vscode/webview-ui/browser/controller.ts
  • packages/kilo-vscode/webview-ui/browser/types.ts
  • packages/kilo-vscode/src/types/messages/extension-messages.ts
  • packages/kilo-vscode/src/types/messages/webview-messages.ts
  • packages/opencode/src/kilocode/tool/browser-open.ts
  • packages/opencode/src/kilocode/tool/browser-open.txt
  • Agent Manager i18n dictionaries and browser/webview fixtures
  • browser-broker, browser-proxy, browser-controller, browser-message unit tests and CLI sandbox/browser tests

Inline publication was blocked: the bot account already has a pending review on this pull request, so the finding above is reported in the summary only.

Fix these issues in Kilo Cloud

Previous Review Summary (commit df41127)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit df41127)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
packages/kilo-vscode/src/services/browser-automation/browser-broker.ts 863 Public HTTPS pages returning HTTP >= 400 are reported as "Local application returned HTTP …" and marked as navigation errors.
Files Reviewed (5 files)
  • packages/kilo-vscode/src/services/browser-automation/browser-broker.ts - 1 issue
  • packages/kilo-vscode/src/services/browser-automation/browser-policy.ts
  • packages/kilo-vscode/src/services/browser-automation/browser-proxy.ts
  • packages/kilo-vscode/src/services/browser-automation/browser-stream.ts
  • packages/opencode/src/kilocode/tool/browser-open.ts

Inline publication was blocked: the bot account already has a pending review on this pull request, so the finding above is reported in the summary only.

Fix these issues in Kilo Cloud


Reviewed by deepseek-v4.1-flash · Input: 0 · Output: 0 · Cached: 0

Review guidance: REVIEW.md from base branch main

…public-https-13618

# Conflicts:
#	packages/kilo-vscode/src/agent-manager/vscode-host.ts
#	packages/kilo-vscode/src/services/browser-automation/browser-broker.ts
#	packages/kilo-vscode/src/services/browser-automation/index.ts
#	packages/kilo-vscode/tests/unit/browser-broker.test.ts
#	packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts
#	packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts
#	packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts
#	packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts
#	packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts
#	packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts
#	packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts
#	packages/kilo-vscode/webview-ui/agent-manager/i18n/fa.ts
#	packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts
#	packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts
#	packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts
#	packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts
#	packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts
#	packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts
#	packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts
#	packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts
#	packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts
#	packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts
#	packages/kilo-vscode/webview-ui/agent-manager/i18n/uk.ts
#	packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts
#	packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts
#	packages/kilo-vscode/webview-ui/src/i18n/en.ts
#	packages/kilo-vscode/webview-ui/src/types/messages/extension-messages.ts
#	packages/opencode/src/kilocode/tool/browser-open.txt
@marius-kilocode

marius-kilocode commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status update: two worktrees, performance, and fixes

I tested the streamed browser again in a real VS Code instance with two Agent Manager worktrees open at the same time (macOS, Apple M4 Pro, Chrome 153).

Works

  • Each worktree gets its own browser context. Cookies, storage, and input stay isolated per worktree.
  • Only the visible worktree streams frames. With the browser panel closed or the Agent Manager tab hidden, zero frames are sent.
  • Time from switching worktrees to the first frame: 11 ms. Time from opening the panel to the first frame: 29 ms.
  • One visible stream runs at about 60 fps and sends about 2.4 MB/s of frame data to the webview.

Measured cost (two live browsers, 15 s samples)

Scenario Chrome CPU VS Code CPU Extension host CPU
One stream visible 42% 61% 12.5%
Agent Manager tab hidden 16.5% 47% 10%
Browser panel closed 15% 18% 3%

Only one stream can be visible in each Agent Manager window, so streaming cost does not grow with the number of worktrees.

Fixed (pushed in 4ef8aeb)

  • Chrome crash recovery: after Chrome exited, every worktree stayed broken until the extension reloaded. The broker now detects the disconnect, shows "The browser stopped unexpectedly. Refresh to start it again.", and starts a new Chrome on the next Refresh or open. A crash of one page's renderer is also detected. Verified in VS Code with both worktrees.

  • Stopped dev server: a refused connection showed "Local application returned HTTP 403". The proxy now returns a marked 502, and the panel shows "Cannot connect to . Make sure the local server is running." Policy denials still return 403.

  • Missing browser with concurrent opens: when several worktrees open at the same time, all of them now get the missing-browser warning, not only the first one.

  • Agent-opened pages stayed blank: a Playwright screenshot restored the viewport during a concurrent stream resize, so every frame was rejected. Screenshots now run in the stream queue.

  • Back and forward buttons added, driven by the real navigation history.

  • http://0.0.0.0:<port> now opens as http://localhost:<port> (pushed in cbf5dfa). 127.0.0.1 and localhost are both secure contexts, but 0.0.0.0 is not, and localhost keeps the origin that cookies, OAuth redirects, and CORS allowlists usually use.

Known limits

  • Hidden pages keep running. Headless Chrome keeps hidden pages at full speed, about 7-8% of a core for a small animated page. Page freeze, window minimize, background tabs, and CPU throttling have no useful effect in headless mode, and a debugger pause breaks Playwright. The only remaining option is to close idle browsers, which loses page state. This is a product decision, so it is not in this PR.
  • New local origin, new context: navigating to a different local origin or port creates a new browser context, so the login state is lost. This is a side effect of the per-context proxy being locked to one local origin, not a security requirement. Public HTTPS pages share one context, so their sessions are kept.
  • Platforms: tested on macOS only. Known limits from code review: Linux as root or in containers without user namespaces (the Chrome sandbox is always on), Alpine/musl (no supported browser build), and networks that require a corporate proxy or PAC (public HTTPS fails). No browser is bundled; system Chrome or a matching Playwright Chromium must be installed on the extension host machine.

Checks

Typecheck, lint, and knip pass. The browser test files pass (97 tests), including new tests for disconnect recovery, page crash, concurrent missing-browser errors, and the unreachable-server message.

…vigation

Restart Chromium after it disconnects and replace crashed pages on refresh.
Report unreachable local servers instead of a proxy HTTP 403.
Serialize agent screenshots with stream resizes so agent-opened pages stream.
Add back and forward buttons and open 0.0.0.0 dev server URLs on loopback.

@WebReflection WebReflection left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this looks good but I wonder if the default should not be localhost instead of 127.0.0.1. Reason being: browsers already handle localhost as if it was https equivalent which is usually the expected developer experience while debugging/developing locally, otherwise simple APIs such as crypto.randomUUID() will fail at 127.0.0.1 or 0.0.0.0. If this is meant though, I am OK with the current choice.

@marius-kilocode

Copy link
Copy Markdown
Collaborator Author

@WebReflection The crypto.randomUUID() concern does not apply to 127.0.0.1, only to 0.0.0.0.

URL Secure context crypto.randomUUID()
http://127.0.0.1:8615/ yes works
http://localhost:8615/ yes works
http://0.0.0.0:8615/ no missing

@marius-kilocode

Copy link
Copy Markdown
Collaborator Author

We can do localhost then, it should be a one line change

Keep the origin that dev tools usually configure, so cookies, OAuth redirects,
and CORS allowlists for localhost apply.
@marius-kilocode

Copy link
Copy Markdown
Collaborator Author

Approach origin and comparison

The streamed approach in this PR follows the deprecated VS Code Browser Preview extension by Kenneth Auchenberg (Microsoft):

That extension launched headless Chromium with Puppeteer, streamed CDP screencast frames as base64 JPEG or PNG, drew each frame on a canvas in a webview, and forwarded CDP input events. Its src/components/screencast/screencast.tsx credits the Chromium DevTools ScreencastView.

This PR keeps that model: BrowserStream runs Page.startScreencast, StreamViewport draws frames on a canvas, and pointer, key, wheel, and clipboard input return as CDP events.

VS Code stopped that model. Its current Integrated Browser embeds a real Chromium through an Electron WebContentsView, proxies CDP, and runs Playwright in the shared process:

We keep streaming instead of WebContentsView for three reasons: the Electron main process owns WebContentsView and this extension host cannot create one, it cannot show a remote or headless page, and streaming keeps the page in a browser that Kilo controls, so BrowserProxy, BrowserPolicy, and BrowserNetwork can police it.

The security layer is the main difference from the old extension. The old extension had no proxy, no origin allowlist, and no per-session isolation. This PR adds BrowserProxy and BrowserPolicy for DNS-time public HTTPS and loopback rules, plus BrowserNetwork for per-origin CDP Fetch grants and approval prompts.

@marius-kilocode

Copy link
Copy Markdown
Collaborator Author

Future Playwright extensions

A later step can point the built-in Playwright MCP server (BrowserAutomationService, kilo-playwright) at the Agent Manager browser instead of letting it start its own. That browser already runs on Playwright, and the broker factory already exposes a CDP endpoint (BrowserContextFactory.debugging, reserved in browser-runtime.ts).

Do not connect Playwright to that endpoint directly. A direct --remote-debugging-port connection is browser-level, it bypasses BrowserProxy and BrowserNetwork, and any local process could then reach private hosts.

The safe design is the one VS Code uses for the Integrated Browser: put a CDP proxy in front of the browser. The proxy multiplexes clients, filters targets per session with BrowserRoute and the broker owner check, and keeps the network policy in the path. External clients then connect with chromium.connectOverCDP(proxyUrl).

Reference:

@WebReflection

WebReflection commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

The crypto.randomUUID() concern does not apply to 127.0.0.1, only to 0.0.0.0.

uhm ... that might be a chromium only thing or maybe that's now the de-facto standard, pretty sure once upon a time 127.0.0.1 wasn't handled the same, but I think localhost is always safe to use for debug, also easier to type/remember, imho.

thanks for the change/check though

P.S. in PyScript we were redirecting all 0.0.0.0 to localhost because Python httpserver starts suggesting 0.0.0.0 by default 🤦

@marius-kilocode
marius-kilocode merged commit da1b983 into main Sep 25, 2026
49 of 52 checks passed
@marius-kilocode
marius-kilocode deleted the browser-public-https-13618 branch September 25, 2026 09:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Sponsor
SponsoredKunjungi sekarang
Promo