本页内容

@mearl/provider

简体中文 | English

@mearl/provider 是 Mearl 的统一浏览器 Provider 契约与运行时。内置 Local Chrome 和外部 Provider 使用相同的 BrowserProviderDefinition;区别只在于运行时位于 Native Host 进程内, 还是通过本地 Provider Socket 接入。

action 清单直接复用 @mearl/browser-core/browserActionProtocol 的能力动作合同,不维护独立生成副本。

核心模型

默认由 Mearl 管理控制会话。Provider 继续接收目标实例、页面与普通 action,无需处理控制会话 ID、资源归属或人工交接状态。带 --session 的释放只结束 Mearl 任务控制;实例关闭、退出接管与 Cookie 同步仍使用 Provider 的生命周期方法。上传与下载分别按 capabilities.actions 中的 page_upload、page_download 声明使用。

普通 action 响应中的 session 由 Mearl 附加,只包含路由与控制状态;browser_control 交接时还包含会话页面清单。page_snapshot 的文本 ref 可直接用于后续操作,Provider 应在自身运行时保存其定位映射,不在响应中输出内部节点 ID 字典。

page_upload 接收执行宿主上的 filePaths,Mearl 负责中转调用方文件与浏览器产物。page_download 接收 { tabId, trigger, url?, timeout? };Provider 先监听下载,再执行点击,等待文件完成后返回 ProviderDownloadResult:{ filename, filePath, action, owned? }。filePath 是本地 Provider 进程宿主上的绝对路径,云端文件由 Provider 取回;action 是点击结果,owned:true 允许 Mearl 在导入后删除该中转文件。Mearl 将结果转为统一的浏览器产物,支持调用方保存、后续上传与会话清理。下载超时或来源不确定时返回错误,不重复点击。

Provider v2 分开表达实例状态与控制方式:

  • status:实例处于 connected、pairing、running_disconnected 或 stopped。
  • control:页面操作由 Provider 自己处理、由宿主的 Extension/CDP 处理,或当前没有控制通道。

实例还会声明 ownership、persistence、当前允许的资源 operations、稳定 references 和可公开展示的 details。因此 Provider identity 不再等同于控制传输,持久化实例 也不需要伪装成已连接浏览器。

外部 Provider 安装

外部 Provider 包将 @mearl/provider 声明为运行依赖:

pnpm add @mearl/provider

包的 package.json#mearl.provider 声明协议入口、浏览器类型和结构化启动参数:

{
  "name": "@example/mearl-provider-device",
  "version": "1.0.0",
  "type": "module",
  "mearl": {
    "provider": {
      "providerId": "example-device",
      "name": "Example device provider",
      "protocolVersion": 3,
      "entry": "./dist/runtime.js",
      "browserType": {
        "name": "Device",
        "help": ["Pair a device, then use its browserId with Mearl actions."],
        "inputSchema": {
          "type": "object",
          "properties": {
            "code": {
              "type": "string",
              "title": "Pairing code",
              "placeholder": "Generated by default"
            }
          }
        }
      }
    }
  }
}

Schema 支持 string、number、boolean、array 和递归 object;Provider 配置也可通过 browserType.configuration.inputSchema 声明。browserType.help 由 mearl <providerId> 和 mearl <providerId> --help 展示,让 Provider 的操作说明随独立包版本演进。同名 Skill 插件已注册时, CLI 会在其后自动追加插件命令摘要,Provider 不需要在 browserType.help 中重复维护命令清单。 local 是内置保留 ID。

Provider 可在浏览器类型、表单字段、选项、连接方式和动态表单提示上通过 locales 声明 多语言文案。例如,字段可设置 title: "任务名称" 和 locales: { en: { title: "Task name" } }。 扩展依次按地区语言(如 zh_CN)、基础语言(如 zh)和字段默认值选择文案。 实例引用可通过 label 和 locales 声明详情标签,通过 summary: true 在折叠行展示标识。

最小外部运行时

import { createProviderServer, type BrowserProviderDefinition } from '@mearl/provider';

const definition: BrowserProviderDefinition = {
  descriptor: {
    providerId: 'example-device',
    name: 'Example device provider',
    version: '1.0.0',
  },
  browserType: {
    name: 'Device',
    inputSchema: {
      type: 'object',
      properties: { code: { type: 'string', title: 'Pairing code' } },
    },
  },
  launch: async options => createDevice(String(options.code ?? '')),
  close: async ({ browserId }) => removeDevice(browserId),
  handleAction: async ({ browserId, action, data }) =>
    dispatchDeviceAction(browserId, action, data),
};

const server = createProviderServer({ definition });
await server.start();
server.updateBrowsers([
  {
    browserId: 'device-1',
    name: 'Example device',
    status: 'connected',
    control: {
      kind: 'provider',
      capabilities: {
        actions: ['tab_list', 'page_snapshot', 'page_click'],
        screenshot: 'none',
      },
    },
    ownership: 'owned',
    persistence: 'persistent',
    operations: ['close'],
  },
]);

配对中的设备使用 status: "pairing" 并提供 pairing;停止的持久化实例使用 status: "stopped" 和 control: { kind: "none" }。运行但尚未接通控制链路时使用 status: "running_disconnected"。 Runtime 会统一校验 schema、状态机、操作能力和页面 action capability,校验通过后才调用实现。 capabilities.actions 应明确列出适配器已经实现并验证的动作,避免通过全量动作减排除项生成清单,使 SDK 新增动作被误报为可用。适配器转发请求时,只有能证明请求尚未发送的连接失败才可自动重连重试;已经发送、响应丢失或业务执行失败的动作不应自动重放。 start、detach、getAccess、syncCookies 等可选实现必须与实例声明的 operation 对应; 这些生命周期方法由 Provider runtime 协调,浏览器动作使用统一的 Mearl 协议。

公共 browser_launch 对所有 Provider 接收 { provider, providerOptions },返回与 browser_list.browsers[] 相同的 ManagedBrowserInfo(对象形式的 Provider identity、状态、 持久化策略、操作和安全元数据)。管理器内部字段、控制连接和临时访问地址不进入该结果。 Local 的 CLI 顶层参数是便捷输入;外部 Provider 参数必须放在 providerOptions,两者不得混用。

内置浏览器保留本地启动交互;外部 Provider 根据 browserType.inputSchema 生成输入项。 两者提交同一结构的 providerOptions。 Schema 的顶层 oneOf 可声明多个完整对象约束, 例如创建与接管会话;每个分支用 title 命名,Runtime 要求输入恰好匹配一个分支, not 可排除不允许的字段组合。default 是表单初始值,不会隐式修改调用方参数。 对象字段 copyCookieDomains 约定使用 { presets?, domains? } Cookie 范围结构。 为这个字段声明 component: "cookie-sync" 后,Mearl 浏览器管理器会使用统一的 Cookie 范围选择组件,并在提交前申请所选站点权限;未声明组件时仍按普通 object 字段渲染。 Provider 可用 component: "switch" 渲染紧凑布尔开关;formHidden 隐藏仅供 CLI/API 使用的高级字段,formWidth 控制字段宽度,formDefault 设置不影响运行时默认值的表单初值, formHideOptional 只隐藏可选标识而不改变校验,formVisibleWhen 根据另一个字段是否为空控制显示与提交。 browserType.documentationUrl 声明 HTTP(S) 文档地址;configuration.inputSchema 声明配置参数, 配置只传给所选 Provider 的 configure,不存入页面本地存储。

Provider 需要查询设备池、已有会话或动态候选项时,声明 browserType.dynamicLaunchForm: true 并实现只读的 getLaunchForm({ variant?, values }, context)。 browserType.launchForm 可提供无网络依赖的初始表单,弹窗立即渲染其字段和模式; 各 variants[] 可用 inputSchema/values/refreshOn/dynamicFields 覆盖该模式的初值。 dynamicFields 指定异步查询期间需要禁用的字段,其余字段与模式选择保持可操作;加载状态在表单状态行显示。 初次查询携带静态表单默认值;查询只传入可解析的字段,最终启动仍严格校验全部输入。 用户在等待期间输入的内容会保留,切换模式或环境会使旧请求失效。 管理器通过内部 browser_providers 的 launch_form operation 获取 ProviderLaunchForm,不识别 Provider ID 或业务字段:

  • inputSchema 描述当前模式的用户输入,required 决定必填标识与提交校验。
  • variants 的 id/title/description 描述连接方式,variant 指定当前方式。
  • values 是 Provider 给出的固定启动参数,提交时与用户输入合并;只包含非敏感参数。
  • 字符串字段的 options 使用 { value, label, description?, disabled? } 描述候选项。 禁用项只供展示;运行时的最终资源状态由 Provider 的 launch 再次核验。
  • refreshOn 指定需要重新查询表单的字段;字段变化会清空其他选项字段的选择。 刷新时保留已有输入,选项字段仅保留仍有效的选择;切换模式重新使用该模式默认值。
  • message 展示查询结果,blocked 阻止无资源、无有效会话等情况下的提交。

查询过程不得创建或租用资源,也不得返回凭据。Runtime 会校验返回的 schema、候选项、 模式与固定参数。最终 launch 仍使用 browserType.inputSchema 校验,动态表单不改变 CLI/API 参数契约。未声明动态表单的 Provider 继续使用静态 schema。 开发联调时,Provider 应链接包含该公开契约的本地 @mearl/provider 构建;分发时需配套发布 包含动态表单能力的 SDK、Mearl 宿主与 Provider。

details 中的通用键具有固定类型:headless 为布尔值,createdAt 为可解析时间字符串, cdpPort 为有效端口,account 包含 nickname 和可选的字符串 accountId/loginId, copiedCookies 包含字符串数组 domains 和非负整数 count。其余键允许非敏感 JSON,界面同样展示。 references 的 kind 表示资源类别,id 为不透明标识,label 为展示名; 通用类别包括 profile/session/image/context/context-name,也允许 Provider 自定义类别。

内置运行时

Native Host 使用 createProviderRuntime(definition) 直接运行内置 Local Provider,不需要创建 Socket 或注册安装记录。内置与外部实现共享同一份 schema 校验、状态机和生命周期派发逻辑;需要宿主服务的进程内调用可通过 ProviderContext.host 注入,Provider 合同本身不依赖 Extension、Native Messaging 或 具体浏览器服务 SDK。

指定 Provider 的启动、配置和动态表单查询只启动该 Provider;完整浏览器发现仍检查所有已安装 Provider。Mearl 的 Provider 控制宿主按调用方绝对期限管理排队及后续动作,旧调用方使用按动作推导的等待时间,持久化关闭和长时间等待不受固定两分钟限制。适配器自己的转发链路也应覆盖对应动作的时间预算,并在等待失败时保留执行结果不确定的语义。

外部 Provider 的安装、发现与启动仍由 @mearl/setup 管理:

npx @mearl/setup provider install <package>
npx @mearl/setup provider update <provider-id>
npx @mearl/setup provider uninstall <provider-id>

Socket 协议

当前 Provider 协议版本为 3,请求 envelope 携带 protocolVersion。安装元数据、 运行时 descriptor、运行中进程与调用方必须使用相同协议;不匹配时更新相关包并重启进程。

  • browser_providers:operation: "configure" 携带 options,operation: "launch_form" 携带 variant? 和 values。
  • browser_instances:operation: "start" | "detach" | "sync_cookies";同步携带 scope,目标实例由 envelope 的 browserId 选择。
  • browser_launch、browser_close、get_browser_access_url 保留各自职责。
  • browser_sessions 由 Mearl 控制进程处理,不转交外部 Provider;会话释放与实例 detach 分开执行。

SDK 负责将 Socket 请求分发到 configure/getLaunchForm/start/detach/syncCookies 等方法。 browser_release 普通 action 不承担实例解绑;公开 CLI 的无会话释放由 Mearl 转换为 detach。 打包 SDK 的 Provider 必须更新 SDK 并重新构建,修改安装元数据不能替换运行中的旧代码。

外部任务控制与页面增强

已自行管理任务归属、人工交接的 Provider,可在 browserType.controlSessionModes 中声明 ["external"],并在实例的 control.capabilities 中返回 controlSessionMode: "external"。 SDK 在服务启动和操作前确认 Mearl 控制宿主支持此模式;旧宿主会明确拒绝。 同一运行时内实例的控制模式固定。该模式不接受 Mearl --session,页面归属和交接仍由外部工具负责。 未声明该字段的 Provider 保持原有行为。

支持统一交接入口时,实现 BrowserProviderDefinition.control(request, context),并在实例的 operations 中声明 handoff、resume、finish 中实际支持的操作。Mearl 将 browser_control 转发给该方法,不创建 Mearl 会话。请求包含 Provider 内的 browserId、operation,以及可选的 tabId、reason;只有 finish 可带 keepTabIds。返回 { controlSessionMode: "external", operation, control },其中 control 分别为 user、agent、finished。Provider 必须校验控制权和保留页归属,拒绝不支持的参数,并让交接预检在页面操作等待期间仍可响应。只有用户明确交还后才可 resume。

外部 Provider 可用 createProviderEnhancementClient(providerId, browserId) 为已获准页面绑定 Mearl Extension 的环境增强,依次调用 bind、invoke、release。这条通道只允许设备、App、时区类增强, 具体范围取决于 Extension 返回的能力;当前 Extension 实现仅开放时区。 它沿用域授权和现有页面控制冲突检查,不登记自动化页面,不提供普通页面操作、任意代码执行或关闭权限。 独立 Provider 包使用 @mearl/provider-<id> 命名。EgoLite 的独立 @mearl/provider-ego 使用外部控制模式,经 Ego 原生通道管理 Space、页面和人工交接;设备和 App 模拟不依赖浏览器扩展。安装及能力限制以 Provider 自身说明为准。

包元数据 mearl.provider.extensionAgentDefault.macBundleIds 可声明浏览器应用的精确 Bundle ID 列表,例如 ["com.example.browser"]。支持该协议的 macOS Native Host 仅匹配启动它的浏览器;命中时扩展默认关闭 Agent 接入,用户的本地明确选择优先。声明随 Provider 注册保存,变更后需重新注册;它不依赖 Provider 进程在线,也不代表 Mearl 获得任何 Space 控制权。不要为通用 Chrome 等应用声明独占默认值,除非该 Provider 确实承担该浏览器所有 Agent 接入。

实现必须提供只读的 getEnhancementTarget({ browserId, tabId }, context):页面由用户控制时返回 { tabId, ready: false };确认 Agent 可操作时返回 { tabId, ready: true, mainFrameId, url },也可用当前物理 targetId 代替 mainFrameId。 身份必须来自当前浏览器目标;提供两项时,当前宿主同时核验两项。仅提供 target ID 时,需要宿主具备相应的目标核验能力。 URL 仅用于缩小候选范围,不能单独证明双方是同一页面。 此回调和 listBrowsers 必须能在 handleAction 等待增强结果期间重入,不应获取该操作持有的锁或认领用户页面。

调用可传入原请求的绝对 deadline,它不属于业务 payload。绑定句柄有页面、连接代次和动作范围限制; 进程重启或页面身份变化后需要重新核验。变更响应丢失后,后续写入会被隔离,不能自动重放或改走普通 action。 显式重新绑定只完成核验;无法确认的环境仍须由执行宿主处理恢复冲突。

已安装 App 资源

外部控制 Provider 可通过 createProviderAppResourceClient(providerId, browserId) 读取本机已接受的 App 定义与运行时。catalog(params?, deadline?) 只返回目录摘要; resolve({providerTabId, profile, options?}, deadline?) 在重新核验页面控制和身份后返回编译配置、 能力/安装 revision,以及必要运行时的确切 binding 与源码。它不执行页面安装或业务宿主服务。

资源由完整 Native 能力服务解析,Skill 目录读取已注册快照;不会为发现目录重新执行 Skill 源文件。 资源协议声明只表示读取口存在;显式读取时才刷新和校验安装,能力模块的慢初始化或失败不阻塞普通 Provider 控制宿主启动。 含桥或全局变量且未显式指定运行时的端模拟配置,沿用已安装的 app.defaultPageRuntime;缺少运行时直接拒绝, 不将其降级为已完整准备的 UA 配置。SDK 会再次核验定义 revision、页面证明和源码 SHA-256。 独立的资源消息上限为 16 MiB,以容纳合法运行时的 JSON 编码;普通增强通道仍限 1 MiB。

Provider 应在自己的实例/页面范围中保存并引用已核验代码,保持原有资源配额与清理规则; Space 清单不等于整个物理浏览器的目标清单,不能据此回收别人的资源。 获得资源不代表后端已支持其根容器、桥回调或真实授权,只有逐项验证后才声明对应 App 能力。 旧宿主缺少资源协议声明时明确拒绝,不回退到普通 action 或任意 Node 执行。