openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
342 lines (341 loc) • 17.4 kB
JavaScript
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js";
import { a as optionalNonNegativeIntegerSchema, c as stringEnum, i as optionalFiniteNumberSchema, o as optionalPositiveIntegerSchema, s as optionalStringEnum } from "./typebox-BdE5GwKs.js";
import "./string-coerce-runtime-GQa0ehRA.js";
import "./channel-actions-CrPVkBaw.js";
import { n as ACT_MAX_VIEWPORT_DIMENSION } from "./act-policy-DHamxe2c.js";
import { Type } from "typebox";
//#region extensions/browser/src/browser-node-commands.ts
const BROWSER_PROXY_COMMAND = "browser.proxy";
const BROWSER_PROXY_UPLOAD_COMMAND = "browser.proxy.upload.v1";
function browserProxyUploadUnavailableMessage(pendingDeclaredCommands) {
return pendingDeclaredCommands?.includes("browser.proxy.upload.v1") ? "browser node remote upload transfer is pending approval; approve the node's pending command update before retrying" : "browser node does not support remote upload transfer; update the node or approve its pending command update before retrying";
}
//#endregion
//#region extensions/browser/src/browser-tool-binding.ts
/** Validate the plugin-owned run binding before any browser route is resolved. */
function parseBrowserTabToolBinding(value) {
if (!value || typeof value !== "object" || Array.isArray(value)) return {
ok: false,
error: "browser tool binding must be an object"
};
const record = value;
const target = record.target === "host" || record.target === "node" ? record.target : void 0;
const node = normalizeOptionalString(record.node);
const profile = normalizeOptionalString(record.profile);
const targetId = normalizeOptionalString(record.targetId);
if (record.kind !== "tab") return {
ok: false,
error: "browser tool binding kind must be \"tab\""
};
if (!Number.isSafeInteger(record.tabId) || Number(record.tabId) < 0) return {
ok: false,
error: "browser tool binding tabId must be a non-negative integer"
};
if (!target || !profile || !targetId || target === "node" && !node) return {
ok: false,
error: "browser tool binding requires target, profile, and targetId"
};
if (target === "host" && node) return {
ok: false,
error: "browser host binding cannot include node"
};
return {
ok: true,
binding: {
kind: "tab",
tabId: Number(record.tabId),
target,
...node ? { node } : {},
profile,
targetId
}
};
}
const BROWSER_TAB_BOUND_ACTIONS = [
"act",
"close",
"console",
"requests",
"errors",
"text",
"emulate",
"dialog",
"download",
"focus",
"navigate",
"pdf",
"screenshot",
"snapshot",
"tabs",
"upload",
"waitfordownload"
];
function bindTargetId(record, targetId) {
const requestedTargetId = normalizeOptionalString(record.targetId);
if (requestedTargetId && requestedTargetId !== targetId) throw new Error("browser action cannot override its run-bound tab target");
const actions = Array.isArray(record.actions) ? record.actions.map((action) => action && typeof action === "object" && !Array.isArray(action) ? bindTargetId(action, targetId) : action) : record.actions;
return {
...record,
targetId,
...actions ? { actions } : {}
};
}
/** Pin model-supplied browser arguments to the trusted tab route for this run. */
function applyBrowserTabToolBinding(input, binding) {
const requestedTarget = normalizeOptionalString(input.target);
const requestedNode = normalizeOptionalString(input.node);
const requestedProfile = normalizeOptionalString(input.profile);
if (requestedTarget && requestedTarget !== binding.target) throw new Error("browser action cannot override its run-bound target");
if (requestedNode && requestedNode !== binding.node) throw new Error("browser action cannot override its run-bound node");
if (requestedProfile && requestedProfile !== binding.profile) throw new Error("browser action cannot override its run-bound profile");
const bound = bindTargetId(input, binding.targetId);
const request = bound.request && typeof bound.request === "object" && !Array.isArray(bound.request) ? bindTargetId(bound.request, binding.targetId) : bound.request;
return {
...bound,
target: binding.target,
...binding.node ? { node: binding.node } : {},
profile: binding.profile,
...request ? { request } : {}
};
}
//#endregion
//#region extensions/browser/src/browser-tool-description.ts
/** Build the Browser tool guidance shared by lazy registration and runtime execution. */
function describeBrowserTool(opts) {
const actions = new Set(opts.capabilities.actions);
const evaluateEnabled = opts.capabilities.actKinds.includes("evaluate");
return [
`Control the browser via OpenClaw's browser control server. Available actions: ${opts.capabilities.actions.join(", ")}.`,
...actions.has("profiles") ? ["Browser choice: omit profile to use the configured default (normally the isolated OpenClaw-managed `openclaw` browser).", "When existing logins/cookies matter, use action=profiles to inspect available profiles, then select the appropriate profile by name. Do not assume a profile name. Use only when the task requires an existing session and the user has authorized it."] : [],
...actions.has("importprofile") ? ["Use action=importprofile on macOS to copy cookies from an authorized Chrome-family system profile into a fresh managed profile; this may show a Keychain consent prompt."] : [],
`For Chrome MCP existing-session profiles, omit timeoutMs on act:type, hover, scrollIntoView, drag, select, and fill; that driver rejects per-call timeout overrides for those actions.${evaluateEnabled ? " act:evaluate supports timeoutMs." : ""}`,
...!opts.capabilities.tabBound ? ["When a node-hosted browser proxy is available, the tool may auto-route to it. Pin a node with node=<id|name> or target=\"node\"."] : [],
"When using refs from snapshot (e.g. e12), keep the same tab: prefer passing targetId from the snapshot response into subsequent actions (act/click/type/etc). For tab operations, targetId also accepts tabId handles (t1) and labels from action=tabs.",
"For multi-step browser work, login checks, stale refs, duplicate tabs, or Google Meet flows, use the bundled browser-automation skill when it is available.",
"For stable, self-resolving refs across calls, use snapshot with refs=\"aria\" (Playwright aria-ref ids). Default refs=\"role\" are role+name-based.",
"Repeated compatible snapshots with stable document identity mark newly appeared ref-bearing elements with [new].",
`navigate returns the loaded page's compact snapshot inline (efficient interactive tier; use action=snapshot for a full snapshot); do not call snapshot after navigate.${opts.capabilities.actKinds.includes("batch") ? " Batch act results that report a cross-document navigation also include fresh page state;" : ""} After a single act that triggers navigation, snapshot before using refs.`,
"Use snapshot+act for UI automation. Avoid act:wait by default; use only in exceptional cases when no reliable UI state exists.",
actions.has("text") ? "For page prose, use action=text with optional selector and maxChars; it reads the first selector match, else article, main, or body. Use efficient snapshots for controls; they omit most prose." : `For page text, use snapshot${evaluateEnabled ? " or a bounded act:evaluate" : ""}; efficient snapshots omit most prose.`,
"Use snapshot query to keep lines matching all whitespace-separated tokens, case-insensitively; matching lines retain element refs.",
...actions.has("requests") ? ["Use requests for the recent network log; filter matches URL/type, limit defaults to 50, and clear=true clears the collected log after reading."] : [],
...actions.has("errors") ? ["Use errors for page errors; limit defaults to 50, clear=true clears after reading."] : [],
...actions.has("emulate") ? ["Use emulate with device, colorScheme, timezoneId, or locale; at least one setting is required."] : [],
...actions.has("upload") ? ["For file chooser uploads, pass the trigger ref with paths in the same upload call when available; use paths-only arming only when a later trigger is intentional. Use inputRef or element to set a file input directly."] : [],
...!opts.capabilities.tabBound ? [`target selects browser location (sandbox|host|node). Default: ${opts.targetDefault}.`, opts.hostHint] : []
].join(" ");
}
//#endregion
//#region extensions/browser/src/browser-tool.schema.ts
/**
* 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.
*/
const BROWSER_ACT_KINDS = [
"batch",
"click",
"clickCoords",
"type",
"press",
"hover",
"scrollIntoView",
"drag",
"select",
"fill",
"resize",
"wait",
"evaluate",
"close"
];
const BROWSER_TOOL_ACTIONS = [
"doctor",
"status",
"start",
"stop",
"profiles",
"importprofile",
"tabs",
"open",
"focus",
"close",
"snapshot",
"screenshot",
"navigate",
"console",
"requests",
"errors",
"text",
"emulate",
"pdf",
"download",
"waitfordownload",
"upload",
"dialog",
"act"
];
const BROWSER_TARGETS = [
"sandbox",
"host",
"node"
];
const BROWSER_SNAPSHOT_FORMATS = ["aria", "ai"];
const BROWSER_SNAPSHOT_MODES = ["efficient"];
const BROWSER_SNAPSHOT_REFS = ["role", "aria"];
const BROWSER_IMAGE_TYPES = ["png", "jpeg"];
const TAB_REFERENCE_DESCRIPTION = "Prefer suggestedTargetId/tabId/label; or raw CDP targetId/prefix.";
function resolveBrowserToolCapabilities(params) {
const evaluateEnabled = params?.evaluateEnabled !== false;
const profileCapabilities = params?.profileCapabilities;
return {
actions: (params?.tabBound ? BROWSER_TAB_BOUND_ACTIONS : BROWSER_TOOL_ACTIONS).filter((action) => (profileCapabilities?.supportsPdf !== false || action !== "pdf") && (profileCapabilities?.supportsRequests !== false || action !== "requests") && (profileCapabilities?.supportsErrors !== false || action !== "errors") && (profileCapabilities?.supportsPageText !== false || action !== "text") && (profileCapabilities?.supportsEmulation !== false || action !== "emulate") && (profileCapabilities?.supportsDownloads !== false || action !== "download" && action !== "waitfordownload")),
actKinds: BROWSER_ACT_KINDS.filter((kind) => (evaluateEnabled || kind !== "evaluate") && (profileCapabilities?.supportsBatchActions !== false || kind !== "batch")),
tabBound: params?.tabBound === true
};
}
function createBrowserActProperties(capabilities) {
const supportsBatch = capabilities.actKinds.includes("batch");
return {
targetId: Type.Optional(Type.String({ description: TAB_REFERENCE_DESCRIPTION })),
ref: Type.Optional(Type.String({ description: "Current snapshot ref." })),
actions: Type.Optional(Type.Array(Type.Object({}, { additionalProperties: true }), supportsBatch ? { description: "Nested batch actions." } : {})),
stopOnError: Type.Optional(Type.Boolean(supportsBatch ? { description: "Stop batch on error (default: true)." } : {})),
doubleClick: Type.Optional(Type.Boolean({ description: "Double-click/clickCoords." })),
button: Type.Optional(Type.String()),
modifiers: Type.Optional(Type.Array(Type.String())),
x: optionalFiniteNumberSchema(),
y: optionalFiniteNumberSchema(),
text: Type.Optional(Type.String()),
submit: Type.Optional(Type.Boolean()),
slowly: Type.Optional(Type.Boolean()),
key: Type.Optional(Type.String({ description: "Escape, Enter, Control+Shift+T; aliases Esc, Return, Del, Ctrl, Cmd." })),
delayMs: optionalNonNegativeIntegerSchema(),
startRef: Type.Optional(Type.String()),
endRef: Type.Optional(Type.String()),
values: Type.Optional(Type.Array(Type.String())),
fields: Type.Optional(Type.Array(Type.Object({}, { additionalProperties: true }))),
width: optionalPositiveIntegerSchema({ maximum: ACT_MAX_VIEWPORT_DIMENSION }),
height: optionalPositiveIntegerSchema({ maximum: ACT_MAX_VIEWPORT_DIMENSION }),
timeMs: optionalNonNegativeIntegerSchema(),
selector: Type.Optional(Type.String()),
url: Type.Optional(Type.String()),
loadState: Type.Optional(Type.String()),
textGone: Type.Optional(Type.String()),
timeoutMs: optionalPositiveIntegerSchema(),
...capabilities.actKinds.includes("evaluate") ? { fn: Type.Optional(Type.String()) } : {}
};
}
/** Provider-compatible Browser tool argument schema. */
function createBrowserToolSchema(capabilities) {
const actProperties = createBrowserActProperties(capabilities);
const actKindDescription = capabilities.actKinds.includes("batch") ? "Act kind; batch uses actions." : "Act kind.";
const BrowserActSchema = Type.Object({
kind: stringEnum(capabilities.actKinds, { description: actKindDescription }),
...actProperties
}, { description: "Nested act request." });
return Type.Object({
action: stringEnum(capabilities.actions),
target: optionalStringEnum(BROWSER_TARGETS),
node: Type.Optional(Type.String()),
profile: Type.Optional(Type.String({ description: capabilities.tabBound ? "Run-bound browser profile." : "Profile; omit for configured default." })),
browser: Type.Optional(Type.String()),
systemProfile: Type.Optional(Type.String()),
into: Type.Optional(Type.String()),
domains: Type.Optional(Type.Array(Type.String())),
targetUrl: Type.Optional(Type.String()),
label: Type.Optional(Type.String()),
limit: optionalPositiveIntegerSchema(),
maxChars: optionalNonNegativeIntegerSchema(),
mode: optionalStringEnum(BROWSER_SNAPSHOT_MODES),
snapshotFormat: optionalStringEnum(BROWSER_SNAPSHOT_FORMATS),
refs: optionalStringEnum(BROWSER_SNAPSHOT_REFS),
interactive: Type.Optional(Type.Boolean()),
compact: Type.Optional(Type.Boolean()),
depth: optionalNonNegativeIntegerSchema(),
frame: Type.Optional(Type.String()),
labels: Type.Optional(Type.Boolean({ description: "Label snapshot/screenshot refs." })),
urls: Type.Optional(Type.Boolean()),
fullPage: Type.Optional(Type.Boolean()),
path: Type.Optional(Type.String()),
element: Type.Optional(Type.String()),
type: optionalStringEnum(BROWSER_IMAGE_TYPES),
level: Type.Optional(Type.String()),
filter: Type.Optional(Type.String()),
clear: Type.Optional(Type.Boolean()),
query: Type.Optional(Type.String()),
device: Type.Optional(Type.String()),
colorScheme: optionalStringEnum([
"dark",
"light",
"no-preference",
"none"
]),
timezoneId: Type.Optional(Type.String()),
locale: Type.Optional(Type.String()),
paths: Type.Optional(Type.Array(Type.String())),
inputRef: Type.Optional(Type.String()),
dialogId: Type.Optional(Type.String()),
accept: Type.Optional(Type.Boolean()),
promptText: Type.Optional(Type.String()),
kind: Type.Optional(stringEnum(capabilities.actKinds, { description: actKindDescription })),
...actProperties,
request: Type.Optional(BrowserActSchema)
});
}
const BrowserSnapshotStatsSchema = Type.Object({
lines: Type.Number(),
chars: Type.Number(),
refs: Type.Number(),
interactive: Type.Number()
}, { additionalProperties: false });
const BrowserBatchAbortSchema = Type.Object({
reason: stringEnum(["navigation", "closed"]),
afterAction: Type.Number(),
url: Type.String(),
skipped: Type.Number()
}, { additionalProperties: false });
/** Common structured result fields returned across Browser tool actions. */
const BrowserToolOutputSchema = Type.Object({
ok: Type.Optional(Type.Boolean()),
targetId: Type.Optional(Type.String()),
url: Type.Optional(Type.String()),
format: Type.Optional(stringEnum(BROWSER_SNAPSHOT_FORMATS)),
snapshot: Type.Optional(Type.String()),
refs: Type.Optional(Type.Union([Type.Number(), Type.Record(Type.String(), Type.Unknown())])),
stats: Type.Optional(BrowserSnapshotStatsSchema),
truncated: Type.Optional(Type.Boolean()),
newElements: Type.Optional(Type.Number()),
tabs: Type.Optional(Type.Array(Type.Object({
suggestedTargetId: Type.Optional(Type.String()),
tabId: Type.Optional(Type.String()),
label: Type.Optional(Type.String()),
targetId: Type.Optional(Type.String()),
title: Type.Optional(Type.String()),
url: Type.Optional(Type.String()),
urlUnavailableReason: optionalStringEnum(["navigation_blocked", "navigation_check_failed"]),
type: Type.Optional(Type.String())
}, { additionalProperties: true }))),
tabCount: Type.Optional(Type.Number()),
results: Type.Optional(Type.Array(Type.Object({
ok: Type.Boolean(),
error: Type.Optional(Type.String()),
navigated: Type.Optional(Type.Literal(true)),
url: Type.Optional(Type.String())
}, { additionalProperties: false }))),
aborted: Type.Optional(BrowserBatchAbortSchema),
pageState: Type.Optional(Type.Object({}, {
additionalProperties: true,
description: "Inline snapshot details attached when the action changed the page document."
})),
enabled: Type.Optional(Type.Boolean()),
running: Type.Optional(Type.Boolean()),
profile: Type.Optional(Type.String()),
driver: Type.Optional(Type.String()),
transport: Type.Optional(Type.String()),
pid: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
cdpPort: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
cdpUrl: Type.Optional(Type.Union([Type.String(), Type.Null()]))
}, { additionalProperties: true });
//#endregion
export { applyBrowserTabToolBinding as a, BROWSER_PROXY_UPLOAD_COMMAND as c, describeBrowserTool as i, browserProxyUploadUnavailableMessage as l, createBrowserToolSchema as n, parseBrowserTabToolBinding as o, resolveBrowserToolCapabilities as r, BROWSER_PROXY_COMMAND as s, BrowserToolOutputSchema as t };