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 ¶
- Variables
- type ConsoleEntry
- type DownloadResult
- type HandoffResult
- type Manager
- func (m *Manager) Click(ctx context.Context, selector string) error
- func (m *Manager) Close(_ context.Context) error
- func (m *Manager) CloseTab(ctx context.Context, index int) error
- func (m *Manager) ConsoleLogs(ctx context.Context, limit int) ([]ConsoleEntry, error)
- func (m *Manager) Download(ctx context.Context, selector, url, destPath string) (DownloadResult, error)
- func (m *Manager) Handoff(ctx context.Context) (HandoffResult, error)
- func (m *Manager) Hotkey(ctx context.Context, keys string) error
- func (m *Manager) Inspect(ctx context.Context, selector string) (string, error)
- func (m *Manager) Navigate(ctx context.Context, url, waitUntil string) (NavigateResult, error)
- func (m *Manager) NetworkRequests(ctx context.Context, limit int) ([]NetworkEntry, error)
- func (m *Manager) Screenshot(ctx context.Context, selector string, fullPage bool) ([]byte, error)
- func (m *Manager) Scroll(ctx context.Context, selector string, deltaX, deltaY float64) error
- func (m *Manager) Status() Status
- func (m *Manager) SwitchTab(ctx context.Context, index int) error
- func (m *Manager) Tabs(ctx context.Context) ([]TabInfo, error)
- func (m *Manager) Type(ctx context.Context, selector, text string, submit bool) error
- func (m *Manager) Upload(ctx context.Context, selector string, paths []string) error
- func (m *Manager) Wait(ctx context.Context, selector, text string, networkIdle bool, ...) error
- type NavigateResult
- type NetworkEntry
- type Status
- type TabInfo
Constants ¶
This section is empty.
Variables ¶
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") // 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 ¶
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) Close ¶
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 ¶
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 ¶
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) NetworkRequests ¶
NetworkRequests mirrors ConsoleLogs exactly.
func (*Manager) Screenshot ¶
func (*Manager) SwitchTab ¶
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 ¶
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) Upload ¶
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 ¶
type NavigateResult struct {
}
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.