UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

342 lines (341 loc) 17.4 kB
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 };