Files
market/skills/web-access/references/browser-tools.md
Yige 0d4d7b208b feat(web-access): v3.0 内置浏览器能力面同步——删除 Python Playwright 回落(S31) (#85)
## 变更内容 / What

浏览器升级计划 S31 最终验收信号:web-access 技能与内置受管浏览器新能力面对齐,**删除「用户手工启动调试 Chrome +
Python Playwright」回落路径**。

- **删除回落路径**:Prerequisites: Chrome CDP Setup、Layer 3 CDP Browser(Python
Playwright 模板)、Installation Note(pip install
playwright)整段删除;references/cdp-browser.md 文件删除;jina-reader.md 的 CDP 引用改为
page.extract-text
- **订正陈旧断言**:
- 「没有批量取文通道」→ BrowserSnapshot mode:text /
page.extract-text(maxBytes/cursor 分页,超出截断给 nextCursor)
- 「page.evaluate 基本不可用」→ 返回真实值(expression/awaitPromise,超预算截断标
truncated);仍走人工闸门
- 「截图前必须 tab.activate / 串行截图 / BROWSER_TAB_HOST_NOT_FOUND」→ S36 订正:Agent
单标签会话免 activate;多标签后台 tab 秒级报 BROWSER_VIEWPORT_UNAVAILABLE;命令超时只 stop 不
close,标签页可重试
  - 「accessibility 超限即失败」→ 尊重 depth + maxBytes 截断翻页(S8)
  - 「只有整页截图」→ clip{x,y,width,height,scale≤4} + captureBeyondViewport
- **provides.tools 加 BrowserScript**(code-mode;信任级别等同 Bash)
- **新增选用规则(D4 唯一约束机制)**:反检测站点一律优先 input.*(#1808 输入拟真 +
身份一致性);page.element 写类仅用于表单批量填充等站点不检测场景;JS 直调 el.click() 为禁止回退
- **新增 fetch.browser 配方**:page.evaluate 页面上下文跑 fetch(带 origin Cookie、同
origin、受 Grant origins 约束)——登录态取站内接口的正解
- **能力速查**:page.element 九 op / page.wait 九 until / inline wait 块 / loc=
方言 / BrowserScript / 跨源 iframe 快照(S35)
- **版本** 2.2.1 → 3.0.0(删除回落层为 breaking);source_hash
重算;required_client_version 维持 10.0.98(新能力在正文标注 10.0.112+)

## Why

v2.x 时代回落路径存在的每一条理由(无批量取文、evaluate 不可用、截图必须串行 activate)均已被
S2–S14/S35/S36 覆盖;文档继续引导用户手工起调试 Chrome 会误导新 Agent 走已废弃路径。

双语同步修改(SKILL.md / SKILL.zh-CN.md heading 数一致,i18n-validate 通过)。

- [x] CLA
2026-08-17 01:25:46 -04:00

20 KiB
Raw Blame History

内置受管浏览器工具速查L3

v2.1 起本层从「cdp-proxy 驱动用户自己的 Chrome」改为「DesireCore 内置受管浏览器」v3.0 补全能力面(批量取文 / 元素操作 / 等待 / 代码模式)并移除 Python Playwright 回落。 每个任务跑在独立 BrowserSpace 里Cookie / Storage / 缓存互不串扰),每个动作都经过 Capability → Grant → Lease → Origin → Host fencing 校验并留下可审计回执。

要求客户端 v10.0.98+page.extract-text / page.element / page.wait / 内联 wait 块 / 跨源 iframe 快照需 v10.0.112+BrowserScript 需包含 S17/S18 的版本。旧的 BrowserListTabs / BrowserNavigate / BrowserEval / BrowserClick / BrowserScreenshot / BrowserScroll / BrowserSetFiles / BrowserCloseTab 与其背后的 cdp-proxy 已停用,调用会直接返回 「该旧 BrowserXxx/cdp-proxy 入口已停用;请改用 BrowserManage、BrowserSnapshot 或 BrowserAct」。

前置条件

  1. 客户端 v10.0.98+~/.desirecore/config/browser.jsonelectron-embeddedstandalone-managed Provider 处于 enabled(默认即开启)
  2. 无需用户手工启动调试 Chrome也无需 Python / Playwright

工具一览

每个工具默认 hidden: true只有 web-access 技能被激活后才暴露给 LLM

BrowserManage

Space / Session / Tab 的生命周期。

BrowserManage:
  action: create_space          # list_spaces | create_space | start_session | join_session
                                # | leave_session | list_sessions | list_tabs | create_tab
                                # | freeze_session | resume_session | close_session
  name: xhs-note                # create_space 用
  persistence: ephemeral        # ephemeral=查完即弃,不落 Profilepersistent=保留登录态
  providerPreference: [electron-embedded]
BrowserManage:
  action: start_session
  spaceId: bsp_xxx
  capabilities:                 # 只申请你真正要用的,最小权限
    - browser.observe.tabs
    - browser.observe.snapshot
    - browser.observe.screenshot
    - browser.navigate.create-tab
    - browser.navigate.url
    - browser.navigate.activate-tab
    - browser.input.pointer.click
    - browser.input.pointer.wheel   # 要滚动加载就必须带上,漏了 input.wheel 会被策略拒绝
    - browser.input.keyboard

显式传 capabilities 就是在做减法:不传时按 agentDefault 全量签发租约,传了就只签这一份 列表。所以别照抄示例——把你这次真正要用的动作对应的能力都列全。

create_space 会触发一次用户确认。任务收尾用 close_session 释放。

BrowserSnapshot

读页面的主通道。四种 mode

BrowserSnapshot:
  mode: semantic        # semantic默认| text | accessibility | visual
  sessionId: bss_xxx    # 当前 Agent 有多个会话时用于消歧
  tabId: btab_xxx       # 非活动标签页时用于消歧
  options: {...}        # 各 mode 专属参数,见下

semantic —— 可交互元素清单button / input / a 等)+ ref 句柄。每行形如 [ref=e12] [loc=...] 按钮 提交

  • loc= 是该元素的稳定选择器S7仅当能产出时才给出五级优先级 data-testid → 唯一 CSS 安全 id → name 属性 → 链接 href剥 query 前缀匹配)→ role+name
  • 跨源 iframeOOPIF的元素并入同一棵树ref 全局连续编号S35
  • 页面重排后旧 ref 会失效——每次交互前重新取快照;带 ref 的命令要同时给签发该 ref 的快照 snapshotId
  • optionsscopeviewport=视口内元素优先截断)、maxElements(默认 400 上限 2000maxBytes(默认 256KB 上限 1MBcursor 翻页、limit

text —— 正文批量取文通道之一S3/S4

BrowserSnapshot:
  mode: text
  options:
    format: markdown       # markdown默认保留标题/列表/表格)| text纯文本
    maxBytes: 65536        # 默认 65536最大 524288超出不报错截断并返回 nextCursor
    cursor: t1:...:...     # 上一页返回的 nextCursor同参数续读
    scope: full_page       # full_page默认| viewport | ref配 ref + snapshotId 取子树)
    includeLinks: true     # 链接目标写进正文(只写 http/https
    includeTables: true    # 表格保留逐行结构

accessibility —— AX 树S8 起尊重 depth、预算截断不炸 options.depth(默认 50 上限 100maxElementsmaxBytescursor。真实内容页不再 BROWSER_RESULT_TOO_LARGE 整体失败——超预算截断给 nextCursor 续读。

visual —— 截图(像素直接进结果): options.formatpng/jpegqualitycaptureBeyondViewport(整页)、 clip={x,y,width,height,scale}(元素级裁剪放大,scale 最大 4

BrowserAct

一次调用一个受管动作。action 分组:

tab.* —— navigate / back / forward / reload / activate / close

BrowserAct:
  action: tab.navigate
  params: { url: https://www.xiaohongshu.com/explore/... }

input.* —— move / click / double-click / drag / wheel / touch / pinch / key / text。走 #1808 输入拟真(坐标派发、拟真轨迹、身份一致性),反检测站点的交互首选

BrowserAct:
  action: input.click
  params:
    ref: e12                    # 语义快照签发的短号 ref不要裸 x/y
    snapshotId: snap-xxx        # 签发该 ref 的快照回执里的 snapshotId必带
BrowserAct:
  action: input.text
  params: { text: 搜索关键词 }
BrowserAct:
  action: input.wheel
  params: { deltaX: 0, deltaY: 720, x: 640, y: 400 }

带元素 ref 的 input.* 必须在 params 顶层同时带同一条快照回执的 snapshotId——ref 序号跨 快照会重复,缺 snapshotId 一律拒绝执行。

page.element —— 判别式元素命令op × selector九 opS10表单批量填充等站点 不检测场景用;反检测站点交互走 input.*

BrowserAct:
  action: page.element
  params:
    op: fill                  # 写类fill | select-option | check | uncheck | scroll-into-view
                              # 读类get-attribute | bounding-box | count | all-inner-texts
    selector: loc=css:input#email   # loc= 方言或 e12 / ref=e12配 snapshotId
    value: user@example.com   # fill / select-option 用
    snapshotId: bsnp_xxx      # selector 用 ref 时必带
  • 写类走 browser.input.keyboard 能力档(与 input.text 同档);读类走 browser.observe.snapshot 只读档
  • fillinput[type=password] 一律拒绝
  • selector 方言:e<序号> / ref=e<序号>loc=css: / loc=role: / loc=text: / loc=testid:、裸 CSS可叠 internal:nth/last/scope/filter。未知前缀(如 loc=xpath:)显式报错,绝不静默降级

page.wait —— 判别式等待until九种S11。等待不改页面状态走只读档

BrowserAct:
  action: page.wait
  params:
    until: networkidle        # load | domcontentloaded | networkidle | selector | url | timeout
                              # | request | response | download
    timeoutMs: 10000          # 默认 10000上限 60000
    idleMs: 500               # networkidle 静默窗,默认 500
  • 轮询型load/domcontentloaded/networkidle/selector/url/timeout超时返回 waited: false,不抛错
  • 事件型request/response/download超时抛错
  • waitForFunction 刻意不在枚举里——任意 JS 走 page.evaluate 的能力档与审批

内联 wait 块S14—— tab.navigate / input.click / input.key / page.element{op:"fill"} 支持 params.wait(形态与 page.wait 参数同构),一条回执完成「动作→等结果」,等待器先于动作注册, 不跨两次 IPC 出竞态:

BrowserAct:
  action: tab.navigate
  params:
    url: https://example.com/login
    wait: { until: networkidle, timeoutMs: 15000 }

page.evaluate —— 页面上下文求值S2 起返回真实值):

BrowserAct:
  action: page.evaluate
  params:
    expression: document.title
    awaitPromise: true        # 默认 true
  • 返回值原样过界(字符串/对象/数组都真实返回);超预算截断并标 truncated,不抛错
  • 能力档 browser.page.evaluate 属人工闸门:非 allow-all 模式每次调用弹审批卡
  • 登录态取站内接口走 [fetch.browser 配方](见下节)

page.extract-text —— 与 BrowserSnapshot mode:text 同款取文通道,作为动作下发: scopeviewport/full_page/ref+ ref + snapshotId、formatmarkdown/text、maxBytes默认 65536 最大 524288、cursor^t1: 续读、includeLinks、includeTables。超出预算截断 + nextCursor。

page.screenshot —— 与 BrowserSnapshot mode:visual 同款:formatqualitycaptureBeyondViewportclip={x,y,width,height,scale}。结果落 artifact store回执给 artifact.id / sha256 / bytes截图像素直接进结果见「截图」节

BrowserScript代码模式S17+S18

一段异步 JS 在 Worker 里跑,通过注入的 page / tab / input / snapshot / console / performance 门面连续下发命令——导航→快照→点击→等待→取文一气呵成,消除逐动作往返:

BrowserScript:
  code: |
    const snap = await snapshot.semantic();
    receipt.log(snap.content);
    await input.click({ ref: 'e12', snapshotId: snap.snapshotId });
    await page.wait({ until: 'networkidle' });
    receipt.log(await page['extract-text']({ format: 'markdown' }));
  sessionId: bss_xxx          # 可选,多会话消歧
  tabId: btab_xxx             # 可选,缺省用会话活动 tab
  totalBudgetMs: 180000       # 默认 180000上限 600000审批等待不计入
  maxCommands: 500            # 每次运行命令条数上限
  • 信任级别等同 BashWorker 不是沙箱,脚本可访问 Node fs/net/child_process。两层审批 脚本源码先过一次与 Bash 同档的人类审批allow-all / ask-external 豁免;脚本源码不可记忆), 其内每个受管浏览器能力再按「能力 × 本次运行」各问一次
  • helper 白名单 24 个:snapshot.* / page.extract-text / tab.* / input.* / page.evaluate / page.wait / page.element / console.read / performance.metrics console.log(page) 可输出各 helper 的 signature/params/example 文档
  • 会话被用户接管、Lease 轮换 ⇒ 硬停:后续命令一律不再下发
  • 需要 browser.* 相应能力;receipt.log(...)console.log 输出文本

BrowserImport需人工审批 + 额外授权

把用户浏览器里的登录态 Cookie 导入当前 Space——唯一的登录态复用通道

前置条件(先看这里,别直接试)browser.import.discover / browser.import.cookies.inspect / browser.import.cookies 三个能力不在 agentDefaultBrowserManage(create_space) 建的 Agent grant 就是按 agentDefault 签的。也就是说 仅靠 create_space / start_session 走不通 BrowserImport,必须由 Host/用户侧另行授予 import 能力(agentElevated 或 Workbench 路径)。没有这层授权就别在这条路上耗——如实告诉用户 「当前无法复用你的登录态」,按无登录态继续或放弃。

动作枚举只有这 6 个:discover | create_plan | dry_run | apply | rollback | list_plans 没有 plan)。完整流程是 discover → create_plan → dry_run → apply

BrowserImport:
  action: create_plan           # 域名授权、来源、冲突策略都在这一步定死
  sourceKind: chromium-profile  # chromium-profile | firefox-profile | safari-profile
                                # | browser-extension | cookie-file
  sourceProfileId: <discover 返回的 ID>
  domains: [xiaohongshu.com]    # 必须逐域显式授权
  conflictStrategy: newer-wins  # keep-target | replace-target | newer-wins | fail-on-conflict

dry_run 先看命中多少条再 apply(只认 create_plan 返回的 planId,前缀 bimp_);写坏了 用 rollback + 同一个 planId 回退;list_plans 查历史。解密与过滤全在 Host 侧完成, Cookie 值不会进入 Agent 上下文或审计日志

BrowserShare

把 Space / Session 委派给另一个 AgentshareMode 可选 snapshot(只读副本)、 copy-on-write(写时复制)、live-shared(实时共享)、handoff(移交控制权)。

fetch.browser 配方:带登录态取接口数据

站内接口(列表、评论、订单等 JSON在登录态下的正解——页面上下文跑 fetch,自动带该 origin 的 Cookie同 origin、受 Grant origins 约束,走 page.evaluate 已有闸门:

BrowserAct:
  action: page.evaluate
  params:
    expression: |
      fetch('/api/v1/comments?page=1&size=20', {
        headers: { accept: 'application/json' }
      }).then(r => r.text())
    awaitPromise: true
  • tab.navigate 到该站任意页面建立 origin 与 Cookie再发 fetch路径写相对路径
  • 只能访问当前 tab origin跨站接口先导航过去
  • 大 JSON 用 .text() 拿原文自己截取,或分页多次取
  • 非 allow-all 模式会弹审批卡page.evaluate 人工闸门),向用户说明用途即可

交互通道选用input.* vs page.element反检测决策

场景 用什么 原因
反检测站点(小红书/微博/B站等的一切点击/输入 input.* 坐标派发 + #1808 拟真轨迹 + 身份一致性UA/UA-CH 无 Electron/Headless 痕迹)
表单批量填充等站点不检测的场景 page.element 写类 一条命令完成 fill/select-option/check比逐元素 input.* 快得多
需要元素属性/坐标/计数/批量文本 page.element 读类 只读档browser.observe.snapshot无写审批
任何「用 JS 直调 el.click()」的想法 禁止 绕开全部拟真投入S10 红线);指针动作只走 input.*

截图:像素直接给你,通常不需要再 Read

BrowserSnapshot mode:visualBrowserAct page.screenshot 会把截图像素作为 image 块 直接放进工具结果——视觉模型当场就能看,不必再调 Read

  • 元素级裁剪放大options.clip={x,y,width,height,scale}scale 最大 4。先用 semantic 快照拿到元素坐标,再截那一块并放大——验证码、小按钮在整页截图里只有几十像素, 看不清时用它。不必走 cdp.raw
  • 整页截图captureBeyondViewport: true(超出视口部分也截)
  • 只有结果里明确写了「截图已保存,但…未附带像素」(超预算/体积过大)或需要原始分辨率时, 才走 result.artifact.absolutePathRead 一次

当前模型不支持视觉输入时,结果会明说「你看不到它的内容」——此时不要凭空描述画面 改用 semantic / text 快照拿页面信息。

视口与 tab.activateS36 起)

Agent 会话actor ≠ user的标签页常驻离屏原位,保住合成表面,不再停放成 1×1

场景 行为
embedded单标签会话 无需 tab.activate 即可截图/取视口presentation 即呈现)
embedded多标签会话的后台 tab 仍需先 tab.activate;未激活的视口快判秒级BROWSER_VIEWPORT_UNAVAILABLE,不再挂满 30s也不会毁掉标签页
standalone 后台 tab 可直接截图

多 Space 依然可以并发导航/快照/输入,互不串扰。

已知边界(照做,别试探)

边界 说明
page.evaluate 走人工闸门 能力 browser.page.evaluate 属 always-human-gate非 allow-all 模式每次弹审批卡。返回值已原样过界(超预算截断标 truncated),登录态取接口走 fetch.browser 配方
cdp.raw 需人工审批 browser.raw_cdp.* 属于永远人工闸门的能力,无人值守流程用不了。元素级裁剪 / 整页截图用 clip / captureBeyondViewport,别走它
page.element 没有 click 指针动作必须走 input.*拟真轨迹、可审计JS 直调 el.click() 是明确禁止的回退
fill 拒绝密码框 input[type=password] 一律拒绝——密码输入只走 input.text(拟真键入)
单条命令 30 s deadline 超时只掐掉这一条命令stop 加载),标签页仍可用,重试即可;不再打掉宿主。等待类命令超时连 stop 都跳过
多标签后台 tab 先 activate embedded 多标签会话里后台 tab 截图前先 tab.activate,否则秒级 BROWSER_VIEWPORT_UNAVAILABLE
用户真实操作会抢控制权 在可见标签页上点击、按键、滚轮、触摸会触发 trusted-user-input 并递增 control epoch打断 Agent 的租约。鼠标只是划过不会(默认策略 intentional-input),所以用户看着页面移动光标是安全的
BrowserScript 信任级别等同 Bash 脚本可访问 Node fs/net/child_process源码过一次与 Bash 同档审批 + 每能力一问;接管/Lease 轮换即硬停
artifact 不要走 /save 该接口会弹系统「另存为」对话框等人点。用回执里的 result.artifact.absolutePath(工具会把它登记进本次会话的可读白名单),不要自己拼路径——${DESIRECORE_ROOT} 这类变量在路径展开里不认(只认 ~ / $HOME / $USERPROFILE),拼出来是相对路径、Read 会报「文件不存在」。默认保留 24 小时

SitePatternRead / SitePatternWrite

参见 SKILL.md 的"站点经验积累"章节。任务结束如果发现新陷阱、新选择器,调用:

SitePatternWrite:
  domain: xiaohongshu.com
  scope: agent           # agent=共享(受 Git 管理可发布user=私有
  mode: merge            # 默认 merge 追加replace 覆盖
  content: |
    ## 已知陷阱
    - 2026-08: ...

含 cookie/token/手机号/邮箱时会自动降级 scope='user'。

错误处理

错误 原因 解决
该旧 BrowserXxx/cdp-proxy 入口已停用 还在调 v2.0 的旧工具 改用 BrowserManage / BrowserSnapshot / BrowserAct
BROWSER_TAB_HOST_NOT_FOUND 标签页宿主真的没了(渲染进程崩溃/销毁) 重建 Session。命令超时不再引发此错误——超时只 stop 这一条命令,标签页保留
BROWSER_COMMAND_DEADLINE_EXCEEDED 单条命令超 30 s 标签页仍停在上一页/空白页,直接重试;慢加载常态不算故障
BROWSER_VIEWPORT_UNAVAILABLE embedded 多标签会话的后台 tab 没有可用视口 tab.activate 再截图(秒级快判,不是挂死)
BROWSER_TOOL_SESSION_FORBIDDEN 会话已关闭/崩溃,或不属于当前 Agent 重新 list_sessions,必要时重建
BROWSER_TOOL_SESSION_AMBIGUOUS 当前 Agent 有多个会话且未传 sessionId 显式传 sessionId
BROWSER_HUMAN_APPROVAL_REQUIRED 触到人工闸门能力evaluate / raw_cdp / 上传下载 / Cookie 导入等) 向用户说明用途并等待审批,或换用无需审批的路径(取文用 extract-text裁剪用 clip
BROWSER_RESULT_TOO_LARGE 回执超过结果上限 text/semantic/accessibility 都有 maxBytes + cursor 分页,调小 maxBytes 续读即可,不再整体失败

调用链路

Agent → BrowserManage/Snapshot/Act/Script → browser-use serviceCapability/Grant/Lease/Policy 校验)
      → BrowserHostelectron-embedded 或 standalone-managed→ Chromium
                                    ↑ 每步产出带 digest 的回执,写入审计事件流