BrowserToolHandler:Puppeteer 浏览器自动化
职责
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:12—export class BrowserToolHandler implements IFullyManagedTool,name = ClineDefaultTool.BROWSER。handlePartialBlock:19— 流式分支:launch 走browser_action_launchask,其他 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:481—page.goto用domcontentloaded + networkidle2,再waitTillHTMLStable轮询 HTML 大小。click:528— 拆坐标、page.mouse.click、监听 request 决定是否等导航。screenshot:441— webp 优先,失败兜底 png,都失败抛错并打遥测。
数据流
launch 分支是 BrowserToolHandler 最完整的流程:校验 url → 审批 → hook → 刷新 browserSession → 开浏览器 → 导航 → 返回截图与日志。关键代码:
// 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:
// 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