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