Skip to content

BrowserToolHandler:Puppeteer 浏览器自动化

源码版本v4.0.10

职责

BrowserToolHandler 是 Cline 操作浏览器的入口,对应 ClineDefaultTool.BROWSER 工具名。模型用 action 参数选具体操作:launch(打开 URL)、click(按坐标点击)、type(在当前焦点输入文本)、scroll_up/scroll_down(滚动页面)、close(关闭浏览器)(class declaration:12-13)。每个动作执行后都会返回一张截图和这段窗口里的 console logs,让模型用视觉理解页面状态。

它本身只是「门面」:接到 tool_use 后做参数校验、审批、hook,然后把动作转交给 config.services.browserSession 这个 BrowserSession 实例。真正的浏览器进程、Puppeteer API、截图编码、远程浏览器连接都在 BrowserSession 里(class BrowserSession:38)。BrowserSession 用 puppeteer-core 连接本地 Chrome 或远程浏览器,通过 connect/launch 拿到 Browser/Page 对象。

这套工具在 Cline 里的位置是「能力类工具(capability tool)」,和 file ops、web fetch 同级,都是被 ToolExecutor 拉起来的一次性执行单元。它特殊的地方在于有状态:一个 Task 里浏览器会话只开一个,launch 后所有后续 click/type/scroll 都复用同一个 Page,直到 close 或出错。这也是为什么 handler 在任何错误分支都调 closeBrowser() —— 浏览器进程不能残留。

设计动机

  • 单会话复用:launch 后 page 复用到 close,避免每次 click 都重新打开,模型也能在多次 action 间保持登录态、滚动位置(launchBrowser:155)。
  • 截图 + 控制台日志双通道:每个 action 完成后,doAction 等控制台静默 500ms 再截图,把视觉 + 日志一起回给模型,弥补纯视觉无法捕获 JS 错误的缺口(doAction:378)。
  • launch 单独审批:开浏览器是高风险动作,launch 走 browser_action_launch 这种独立的 ask 类型,其他 action(click/type/scroll)只要 launch 已批准就默认通过(launch flow:81)。
  • 远程浏览器兜底:远程浏览器连接失败时降级到本地模式,不让整个工具崩(remote fallback:181-198)。
  • click 后等导航:点击可能触发页面跳转,doAction 在 click 时监听 request 事件判断是否有网络活动,有就 waitForNavigation(click:528-561).
  • 强制关浏览器才能换工具:返回结果里附一句「REMEMBER ... 必须先 close 浏览器才能用非 browser_action 工具」,提醒模型别在浏览器开着时切去改文件(result reminder:190-194)。

关键文件

  • class declaration:12export class BrowserToolHandler implements IFullyManagedTool,name = ClineDefaultTool.BROWSER
  • handlePartialBlock:19 — 流式分支:launch 走 browser_action_launch ask,其他 action 直接 say 当前坐标/文本。
  • execute:62 — 主流程:校验 action → launch 分支或 click/type/scroll/close 分支 → 返回 screenshot + logs。
  • launch flow:81-127 — launch 的完整流程:校验 url、审批、PreToolUse hook、applyLatestBrowserSettings 刷新 session、launchBrowser + navigateToUrl
  • click/type param check:130-145 — click 缺 coordinate、type 缺 text 时各自报错并 closeBrowser。
  • action dispatch:163-179 — switch 分发到 browserSession.click/type/scrollDown/scrollUp/closeBrowser
  • result format:184-203 — launch/click/type/scroll 返回 screenshot + logs,close 返回纯文本提示。
  • error close:205-208 — 任何异常都 closeBrowser,不残留浏览器进程。
  • launchBrowser:155 — 远程优先,失败降级本地,然后 browser.newPage() 开新 tab。
  • doAction:378 — 挂 console/pageerror 监听、跑 action、等静默 500ms、截图、清理 listener。
  • navigateToUrl:481page.gotodomcontentloaded + networkidle2,再 waitTillHTMLStable 轮询 HTML 大小。
  • click:528 — 拆坐标、page.mouse.click、监听 request 决定是否等导航。
  • screenshot:441 — webp 优先,失败兜底 png,都失败抛错并打遥测。

数据流

launch 分支是 BrowserToolHandler 最完整的流程:校验 url → 审批 → hook → 刷新 browserSession → 开浏览器 → 导航 → 返回截图与日志。关键代码:

typescript
// apps/vscode/src/core/task/tools/handlers/BrowserToolHandler.ts
if (action === "launch") {
    if (!url) {
        config.taskState.consecutiveMistakeCount++
        const errorResult = await config.callbacks.sayAndCreateMissingParamError(this.name, "url")
        await config.services.browserSession.closeBrowser()
        return errorResult
    }
    config.taskState.consecutiveMistakeCount = 0
    // ... approval + PreToolUse hook ...
    await config.callbacks.say("browser_action_result", "")
    config.services.browserSession = await config.callbacks.applyLatestBrowserSettings()
    await config.services.browserSession.launchBrowser()
    browserActionResult = await config.services.browserSession.navigateToUrl(url)
}

注意 config.services.browserSession = await config.callbacks.applyLatestBrowserSettings() 这一行:它不只刷新设置,还把 ToolExecutor 持有的 browserSession 引用替换掉,这样后续 browser tool 调用拿到的才是同一个新 session(applyLatestBrowserSettings:125)。

navigateToUrl 内部走 doAction,所有非 launch action 也都走 doAction。doAction 把截图 + 控制台日志组合成 BrowserActionResult:

typescript
// apps/vscode/src/services/browser/BrowserSession.ts
const screenshotType = this.useWebp ? "webp" : "png"
let screenshotBase64 = await this.page.screenshot({ ...options, type: screenshotType })
let screenshot = `data:image/${screenshotType};base64,${screenshotBase64}`
if (!screenshotBase64) {
    screenshotBase64 = await this.page.screenshot({ ...options, type: "png" })
    screenshot = `data:image/png;base64,${screenshotBase64}`
}
return {
    screenshot,
    logs: logs.join("\n"),
    currentUrl: this.page.url(),
    currentMousePosition: this.currentMousePosition,
}

handler 把这个 result 包成 formatResponse.toolResult 返回,并附上那段强提醒「close 浏览器才能切回其他工具」。

边界与失败

  • action 非法:block 完成时 action 不在 browserActions 列表里,consecutiveMistakeCount++ + sayAndCreateMissingParamError + closeBrowser(invalid action:69-75)。
  • launch 缺 url:launch 必须带 url,缺了同样 mistake++ 和 closeBrowser(missing url:82-87)。
  • click 缺 coordinate / type 缺 text:对应参数缺失,报错并 closeBrowser,不让 session 残留(param checks:131-145)。
  • 远程浏览器连接失败:launchRemoteBrowser 抛错时,catch 后降级到 launchLocalBrowser,遥测记录 remote_browser_launch_error(remote fallback:181-198)。
  • 截图失败:webp 失败时切 png 重试,两次都失败抛 "Failed to take screenshot." 并打 screenshot_error 遥测(screenshot retry:448-466)。
  • page 未启动:doAction 检查 this.page 不存在时抛错,提示「Browser is not launched」,这种情况通常是非 browser_action 工具(其实没有权限)或前次 close 后又调(page check:379-383)。
  • 任何异常都关浏览器:handler 的 try/catch 兜底是 browserSession.closeBrowser(),保证不会留一个 Chrome 进程占资源(error close:205-208)。

小结

BrowserToolHandler 是个薄壳,真正干活的是 BrowserSession。模型通过 action 参数选 launch/click/type/scroll/close,handler 做参数校验和审批,把动作转给 BrowserSession 的 Puppeteer 调用,然后把截图和 console 日志打包回给模型。launch 单独审批,其他 action 复用已开的会话;任何错误都强制关浏览器。

想往里钻可以接着看:

  • 文件与命令类工具:/cap-tools/file-ops
  • Web fetch/search:/cap-tools/web
  • 工具调度链路:/agent-loop/task-class

对照官方资料:Cline 文档 · README