browser

package
v0.5.0-rc.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 19, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Overview

Package browser drives a real Chromium/Chrome instance over the Chrome DevTools Protocol via go-rod/rod, giving the agent's browser_* tools a pure-Go backend with no Node.js or Playwright dependency. See docs/tools.md and docs/security-and-allow-lists.md for the tool contract and safety model.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNoBinaryFound means no system Chrome/Chromium/Edge binary was
	// found on PATH or in well-known install locations. Never triggers an
	// automatic download — see findChromeBinary.
	ErrNoBinaryFound = errors.New("no Chrome/Chromium binary found")

	// ErrLaunchFailed means a binary was found but the browser process
	// failed to start or the initial CDP connection failed.
	ErrLaunchFailed = errors.New("browser launch failed")

	// ErrNavigationTimeout means a navigation or wait-for-load-state
	// condition did not complete before its deadline.
	ErrNavigationTimeout = errors.New("navigation timed out")

	// ErrSelectorNotFound means a CSS selector did not resolve to an
	// element before its deadline.
	ErrSelectorNotFound = errors.New("selector not found")

	// ErrActionDenied means the manager's session was already torn down
	// by an explicit browser_close. Once closed, a Manager never
	// relaunches — close only ever reduces capability, per the safety
	// model documented in docs/security-and-allow-lists.md.
	ErrActionDenied = errors.New("action denied: browser session is closed")

	// ErrTabNotFound means a browser_switch_tab index didn't resolve to a
	// currently tracked page (out of range, or the tab closed since the
	// caller last listed tabs).
	ErrTabNotFound = errors.New("tab index not found")

	// ErrNoDisplay means a visible browser window was requested
	// (browser_handoff) on a host with no display server to show it on —
	// an SSH session, container, or CI box. The isolated headless session
	// is left untouched; the human has to complete the step some other
	// way.
	ErrNoDisplay = errors.New("no display available for a visible browser window")

	// ErrBlockedURL means the URL the agent asked to open uses a scheme the
	// browser tools refuse (file:, chrome:, javascript:, data:, …). Only
	// http, https, and about:blank are allowed — see checkNavigableURL.
	ErrBlockedURL = errors.New("blocked URL")

	// ErrDownloadFailed means a triggered download never completed
	// before the action deadline — the click/navigate that should have
	// started it didn't, or the browser never reported completion.
	ErrDownloadFailed = errors.New("download did not complete before deadline")
)

Stable error taxonomy the browser_* tools map to user-facing messages. Every error this package returns wraps exactly one of these via %w, so callers can classify failures with errors.Is regardless of the underlying rod/CDP error type.

Functions

This section is empty.

Types

type ConsoleEntry

type ConsoleEntry struct {
	Level string // "log", "debug", "info", "warning", "error", "exception", ...
	Text  string
	At    time.Time
}

ConsoleEntry is one captured console.* call or uncaught JS exception, returned by browser_console_logs. At is stamped locally (time.Now()) when the event is received rather than taken from CDP's own timestamp fields, which are session-local monotonic values, not wall-clock.

type DownloadResult

type DownloadResult struct {
	Path              string
	SuggestedFilename string
	SizeBytes         int64
}

DownloadResult is the outcome of a successful browser_download call — the file is already at Path by the time this is returned.

type HandoffResult

type HandoffResult struct {
	// URL is the page the visible window is showing (or was asked to show).
	// Empty when there was no earlier page to carry over.
	URL string
	// AlreadyVisible is true when the session was already headed, so
	// nothing was relaunched.
	AlreadyVisible bool
	// LoadWarning is non-empty when the visible window opened but the page
	// didn't finish loading before the action deadline. The window is still
	// there and the human can usually proceed anyway.
	LoadWarning string
}

HandoffResult is the outcome of a successful browser_handoff call.

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager owns the session-scoped browser lifecycle: at most one launched Chrome/Chromium process and one active page, lazily started on the first action call. Every exported method takes mu for its full duration — including the underlying CDP round-trip — because there is exactly one page to drive; overlapping calls would race on it with no benefit, so serializing here is simpler and safer than trying to prove any subset of calls is independent. This is also what a concurrent `go test -race` exercise over Manager is checking.

func NewManager

func NewManager() *Manager

NewManager returns an unlaunched Manager. The browser process starts lazily on the first action call that needs it (see ensureLocked); browser_status and browser_wait never trigger a launch.

func (*Manager) Click

func (m *Manager) Click(ctx context.Context, selector string) error

func (*Manager) Close

func (m *Manager) Close(_ context.Context) error

Close tears down the browser process and removes its temp profile directory. Idempotent: safe to call multiple times (an explicit browser_close, then again via CleanupTool.Cleanup at session exit). Once closed, the manager never relaunches — every later action call returns ErrActionDenied instead of silently starting a new browser.

func (*Manager) CloseTab

func (m *Manager) CloseTab(ctx context.Context, index int) error

CloseTab closes one tracked tab by index without tearing down the whole session. Like SwitchTab, it never launches — closing a tab that doesn't exist yet makes no sense — but unlike SwitchTab it does a real CDP round-trip, so it gets the same action timeout every mutating call gets.

func (*Manager) ConsoleLogs

func (m *Manager) ConsoleLogs(ctx context.Context, limit int) ([]ConsoleEntry, error)

ConsoleLogs never launches, like Tabs — an unlaunched session has nothing buffered yet. Pure in-memory read, no action timeout needed.

func (*Manager) Download

func (m *Manager) Download(ctx context.Context, selector, url, destPath string) (DownloadResult, error)

Download triggers a download — by clicking selector, or by navigating directly to url (exactly one must be set) — and saves it to destPath, which callers must have already validated the same way write_file does (see BrowserDownloadTool). It owns the whole sequence: a scratch temp dir for Chrome's own GUID-named download file, the CDP download arm/wait (session's downloadViaClick/downloadViaURL), then moving the result to destPath and cleaning up the temp dir.

func (*Manager) Handoff

func (m *Manager) Handoff(ctx context.Context) (HandoffResult, error)

Handoff swaps the headless session for a visible one so a human can complete a step the headless browser can't show them — typically a bot-verification challenge ("Press & Hold"). It never solves or bypasses anything: it only makes the same kind of isolated session visible and reopens the current page in it.

The visible session is a fresh isolated profile, not a copy of the headless one. Chrome can't switch modes in place, and carrying the headless session's cookies across would also carry whatever "this client is automated" verdict the site already attached to them — the human needs a clean start. Only the current URL carries over.

The new browser is launched before the old one is torn down, so a launch failure (no display, Chrome won't start) leaves the existing headless session exactly as it was. Once headed, the session stays headed for the rest of its life: the site's verification is tied to that browser, so dropping back to headless afterwards would just re-trigger it.

func (*Manager) Hotkey

func (m *Manager) Hotkey(ctx context.Context, keys string) error

func (*Manager) Inspect

func (m *Manager) Inspect(ctx context.Context, selector string) (string, error)

func (*Manager) Navigate

func (m *Manager) Navigate(ctx context.Context, url, waitUntil string) (NavigateResult, error)

func (*Manager) NetworkRequests

func (m *Manager) NetworkRequests(ctx context.Context, limit int) ([]NetworkEntry, error)

NetworkRequests mirrors ConsoleLogs exactly.

func (*Manager) Screenshot

func (m *Manager) Screenshot(ctx context.Context, selector string, fullPage bool) ([]byte, error)

func (*Manager) Scroll

func (m *Manager) Scroll(ctx context.Context, selector string, deltaX, deltaY float64) error

func (*Manager) Status

func (m *Manager) Status() Status

Status never launches the browser — see the Status doc comment.

func (*Manager) SwitchTab

func (m *Manager) SwitchTab(ctx context.Context, index int) error

SwitchTab changes which tracked page subsequent actions act on. Like Tabs, it never launches — switching among tabs that don't exist yet makes no sense.

func (*Manager) Tabs

func (m *Manager) Tabs(ctx context.Context) ([]TabInfo, error)

Tabs never launches the browser, like Status — an unlaunched session has no tabs to report. Unlike Status, each entry costs a live CDP round-trip (to read that page's current title/URL), so it's bounded by the same action timeout every mutating call gets, even though nothing is mutated.

func (*Manager) Type

func (m *Manager) Type(ctx context.Context, selector, text string, submit bool) error

func (*Manager) Upload

func (m *Manager) Upload(ctx context.Context, selector string, paths []string) error

Upload sets a file input element's files, lazily launching the browser like every other mutating action. Callers must have already validated every path is inside the trusted workspace — see BrowserUploadTool, which reuses the same write-path boundary as write_file: handing a local file's bytes to whatever origin the active page is on is at least as sensitive as writing it.

func (*Manager) Wait

func (m *Manager) Wait(ctx context.Context, selector, text string, networkIdle bool, timeout time.Duration) error

Wait deliberately does NOT launch the browser: waiting is defined as blocking until an already-visible condition is met, and a session that was never launched has nothing to wait for. Launching a process is a real state change, which is exactly what browser_wait's no-approval exemption assumes it never does. A non-positive timeout (the "built-in timeout" the tool docs promise) falls back to m.timeout() rather than waiting unbounded — the same policy every other action gets automatically.

type NavigateResult struct {
	URL   string
	Title string
}

NavigateResult is the outcome of a successful browser_navigate call.

type NetworkEntry

type NetworkEntry struct {
	RequestID  string
	Method     string
	URL        string
	Status     int
	StatusText string
	MIMEType   string
	Failed     bool
	ErrorText  string
	At         time.Time
}

NetworkEntry is one captured request, returned by browser_network_requests. Status/StatusText/MIMEType stay zero until a response arrives; Failed/ErrorText are set instead if the request never got one. At is stamped locally at request-start, same reasoning as ConsoleEntry.At.

type Status

type Status struct {
	BinaryPath string
	Active     bool
	CurrentURL string
	ProfileDir string
	// TabCount is the number of tracked pages (main page plus any
	// popup/tab opened since launch). 0 when no session is active.
	TabCount int

	// Headed reports whether the active session is showing a visible
	// window (after browser_handoff). False for a headless session and
	// when no session is active.
	Headed bool
}

Status is a point-in-time snapshot of the manager's lifecycle state, returned by browser_status. Computing it never launches a browser or otherwise changes state — it only reports the binary path (discovered on demand, cheaply, via PATH/well-known-location checks) and whatever session is already running.

type TabInfo

type TabInfo struct {
	Index  int
	ID     string
	Title  string
	URL    string
	Active bool
}

TabInfo is a point-in-time snapshot of one tracked page, returned by browser_tabs. Indexes identify the current point-in-time listing; closing a tab can shift later indexes, so callers should list again before acting on a stale index.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL