From a982f798ca20d74b7c3a443f6901c24734de5ba3 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 4 Jun 2026 07:24:31 -0400 Subject: [PATCH] docs: document browser tool runtime --- extensions/browser/src/browser-control-state.ts | 13 +++++++++++++ .../browser/src/browser-gateway-contract.ts | 8 ++++++++ extensions/browser/src/browser-runtime.ts | 6 ++++++ extensions/browser/src/browser-tool.actions.ts | 15 +++++++++++++++ extensions/browser/src/browser-tool.runtime.ts | 7 +++++++ extensions/browser/src/browser-tool.schema.ts | 7 +++++++ extensions/browser/src/browser-tool.ts | 7 +++++++ 7 files changed, 63 insertions(+) diff --git a/extensions/browser/src/browser-control-state.ts b/extensions/browser/src/browser-control-state.ts index f0d632ee10ce..5bf03d2af193 100644 --- a/extensions/browser/src/browser-control-state.ts +++ b/extensions/browser/src/browser-control-state.ts @@ -1,3 +1,9 @@ +/** + * Shared in-process browser control runtime state. + * + * The HTTP server path and background control service both reuse this singleton + * so local tools can attach to the same browser runtime without racing owners. + */ import type { Server } from "node:http"; import { createBrowserRuntimeState, stopBrowserRuntime } from "./browser/runtime-lifecycle.js"; import { type BrowserServerState, createBrowserRouteContext } from "./browser/server-context.js"; @@ -11,6 +17,7 @@ export function getBrowserControlState(): BrowserServerState | null { return state; } +/** Create a route context bound to the current shared browser runtime. */ export function createBrowserControlContext() { return createBrowserRouteContext({ getState: () => state, @@ -18,6 +25,7 @@ export function createBrowserControlContext() { }); } +/** Start or attach the shared browser runtime for either the server or service owner. */ export async function ensureBrowserControlRuntime(params: { server?: Server | null; port: number; @@ -27,6 +35,8 @@ export async function ensureBrowserControlRuntime(params: { }): Promise { if (state) { if (params.server) { + // A foreground server takes ownership of the already-started service + // runtime so shutdown and port reporting follow the visible server. state.server = params.server; state.port = params.port; state.resolved = { ...params.resolved, controlPort: params.port }; @@ -45,6 +55,7 @@ export async function ensureBrowserControlRuntime(params: { return state; } +/** Stop the shared browser runtime when the requesting owner is allowed to do so. */ export async function stopBrowserControlRuntime(params: { requestedBy: BrowserControlOwner; closeServer?: boolean; @@ -55,6 +66,8 @@ export async function stopBrowserControlRuntime(params: { return; } if (params.requestedBy === "service" && current.server && owner === "server") { + // The background service must not close a runtime currently claimed by the + // visible HTTP server; otherwise CLI/browser calls lose their control port. return; } await stopBrowserRuntime({ diff --git a/extensions/browser/src/browser-gateway-contract.ts b/extensions/browser/src/browser-gateway-contract.ts index 48263b48cbca..d862b8bcf249 100644 --- a/extensions/browser/src/browser-gateway-contract.ts +++ b/extensions/browser/src/browser-gateway-contract.ts @@ -1,3 +1,11 @@ +/** + * Gateway method and scope constants for browser proxy requests. + * + * Node-hosted browser control uses these values on both sides of the gateway + * contract, so keep them as literal exports instead of duplicated strings. + */ export const BROWSER_REQUEST_GATEWAY_METHOD = "browser.request" as const; +/** Admin scope required to proxy browser-control requests through Gateway. */ export const BROWSER_REQUEST_GATEWAY_SCOPE = "operator.admin" as const; +/** Scope tuple shape consumed by Gateway tool registration. */ export const BROWSER_REQUEST_GATEWAY_SCOPES = [BROWSER_REQUEST_GATEWAY_SCOPE] as const; diff --git a/extensions/browser/src/browser-runtime.ts b/extensions/browser/src/browser-runtime.ts index 83642449105b..3f76244edd5e 100644 --- a/extensions/browser/src/browser-runtime.ts +++ b/extensions/browser/src/browser-runtime.ts @@ -1,3 +1,9 @@ +/** + * Public browser runtime barrel. + * + * Exposes the browser control server, client helpers, config resolution, and + * route/runtime primitives used by the plugin entrypoints and local CLI. + */ export { startBrowserBridgeServer, stopBrowserBridgeServer } from "./browser/bridge-server.js"; export type { BrowserBridge } from "./browser/bridge-server.js"; export { diff --git a/extensions/browser/src/browser-tool.actions.ts b/extensions/browser/src/browser-tool.actions.ts index e85e39744475..819de7c5978a 100644 --- a/extensions/browser/src/browser-tool.actions.ts +++ b/extensions/browser/src/browser-tool.actions.ts @@ -1,3 +1,9 @@ +/** + * Browser agent tool action executors. + * + * Converts model-facing parameters into browser control client calls and wraps + * browser-originated text as untrusted content before returning it to agents. + */ import type { AgentToolResult } from "openclaw/plugin-sdk/agent-core"; import { readNonNegativeIntegerParam, @@ -102,6 +108,8 @@ function withConfiguredActTimeout( return request; } if (existingSessionRejectsActTimeout(request) && usesExistingSessionProfile(profileName)) { + // Chrome MCP existing-session actions reject per-call timeouts for these + // operations, so default timeout injection must stay disabled there. return request; } @@ -197,6 +205,8 @@ function wrapBrowserExternalJson(params: { includeWarning?: boolean; }): { wrappedText: string; safeDetails: Record } { const extractedText = JSON.stringify(params.payload, null, 2); + // Browser tabs, snapshots, and console output are page-controlled data. Keep + // text wrapped even when details carry the structured fields for callers. const wrappedText = wrapExternalContent(extractedText, { source: "browser", includeWarning: params.includeWarning ?? true, @@ -332,6 +342,7 @@ export async function executeTabsAction(params: { return formatTabsToolResult(tabs); } +/** Execute and format browser snapshots for agent consumption. */ export async function executeSnapshotAction(params: { input: Record; baseUrl?: string; @@ -380,6 +391,8 @@ export async function executeSnapshotAction(params: { : hasMaxChars ? maxChars : undefined; + // AI snapshots have a compact default cap; ARIA snapshots keep full structure + // unless maxChars is explicit, because agents often need complete node refs. const snapshotTimeoutMs = readPositiveIntegerParam(input, "timeoutMs", { message: "timeoutMs must be a positive integer.", @@ -520,6 +533,7 @@ export async function executeSnapshotAction(params: { } } +/** Execute browser console retrieval and wrap page-controlled messages. */ export async function executeConsoleAction(params: { input: Record; baseUrl?: string; @@ -549,6 +563,7 @@ export async function executeConsoleAction(params: { return formatConsoleToolResult(result); } +/** Execute browser actions with profile-aware timeout defaults and stale-tab recovery. */ export async function executeActAction(params: { request: BrowserActRequest; baseUrl?: string; diff --git a/extensions/browser/src/browser-tool.runtime.ts b/extensions/browser/src/browser-tool.runtime.ts index 06ba317b656a..161dfb54b7b1 100644 --- a/extensions/browser/src/browser-tool.runtime.ts +++ b/extensions/browser/src/browser-tool.runtime.ts @@ -1,6 +1,13 @@ +/** + * Runtime dependency barrel for the Browser agent tool. + * + * Kept separate from browser-tool.ts so tests can mock the tool boundary while + * production still imports SDK helpers and browser client actions lazily. + */ import { getRuntimeConfig } from "./sdk-config.js"; export { getRuntimeConfig }; +/** Resolve global image downscaling for screenshots returned to agent tools. */ export function resolveRuntimeImageSanitization(): { maxDimensionPx: number } | undefined { const configured = getRuntimeConfig().agents?.defaults?.imageMaxDimensionPx; if (typeof configured !== "number" || !Number.isFinite(configured)) { diff --git a/extensions/browser/src/browser-tool.schema.ts b/extensions/browser/src/browser-tool.schema.ts index e85a843012c3..31bb35ad73f3 100644 --- a/extensions/browser/src/browser-tool.schema.ts +++ b/extensions/browser/src/browser-tool.schema.ts @@ -1,3 +1,9 @@ +/** + * JSON schema for the Browser agent tool. + * + * The schema stays intentionally flat because provider function-tool validators + * reject several nested union shapes that TypeBox can otherwise emit. + */ import { optionalFiniteNumberSchema, optionalNonNegativeIntegerSchema, @@ -99,6 +105,7 @@ const BrowserActSchema = Type.Object({ // IMPORTANT: OpenAI function tool schemas must have a top-level `type: "object"`. // A root-level `Type.Union([...])` compiles to `{ anyOf: [...] }` (no `type`), // which OpenAI rejects ("Invalid schema ... type: None"). Keep this schema an object. +/** Provider-compatible Browser tool argument schema. */ export const BrowserToolSchema = Type.Object({ action: stringEnum(BROWSER_TOOL_ACTIONS), target: optionalStringEnum(BROWSER_TARGETS), diff --git a/extensions/browser/src/browser-tool.ts b/extensions/browser/src/browser-tool.ts index 901fd385e35d..98efe6975500 100644 --- a/extensions/browser/src/browser-tool.ts +++ b/extensions/browser/src/browser-tool.ts @@ -1,3 +1,9 @@ +/** + * Browser agent tool registration. + * + * Builds the model-facing browser tool, chooses sandbox/host/node routing, and + * maps high-level actions onto browser control client calls. + */ import crypto from "node:crypto"; import { executeActAction, @@ -441,6 +447,7 @@ function readToolTimeoutMs(params: Record) { }); } +/** Create the Browser tool exposed to agents. */ export function createBrowserTool(opts?: { sandboxBridgeUrl?: string; allowHostControl?: boolean;