BrowserToolHandler : automation de navigateur avec Puppeteer
Responsabilités
BrowserToolHandler est le point d'entrée de Cline pour interagir avec le navigateur, correspondant au nom d'outil ClineDefaultTool.BROWSER. Le modèle sélectionne l'action via le paramètre action : launch (ouvrir une URL), click (clic par coordonnées), type (saisir du texte au focus courant), scroll_up/scroll_down (faire défiler la page), close (fermer le navigateur) (class declaration:12-13). Chaque action renvoie une capture d'écran ainsi que les logs console de la fenêtre, afin que le modèle comprenne visuellement l'état de la page.
Ce handler n'est qu'une façade : à la réception d'un appel d'outil (tool_use), il valide les paramètres, gère l'approbation et les hooks, puis délègue l'action à config.services.browserSession, une instance de BrowserSession. Le processus navigateur réel, l'API Puppeteer, l'encodage des captures et la connexion aux navigateurs distants vivent dans BrowserSession (class BrowserSession:38). BrowserSession utilise puppeteer-core pour se connecter à un Chrome local ou distant, et obtient un objet Browser/Page via connect/launch.
Dans l'architecture de Cline, cet outil est un « capability tool » (outil de capacité), au même rang que file ops et web fetch : une unité d'exécution ponctuelle invoquée par ToolExecutor. Sa particularité est d'être stateful : au sein d'un Task, une seule session navigateur est ouverte, et après launch tous les click/type/scroll suivants réutilisent le même Page, jusqu'à close ou une erreur. C'est pourquoi le handler appelle closeBrowser() sur chaque branche d'erreur — aucun processus navigateur ne doit subsister.
Motivation de conception
- Réutilisation d'une session unique : après launch, le Page est réutilisé jusqu'à close, ce qui évite de rouvrir le navigateur à chaque click et permet au modèle de conserver son état de connexion et sa position de défilement entre actions (
launchBrowser:155). - Double canal capture + logs console : après chaque action,
doActionattend 500 ms de silence console avant la capture, et renvoie vision + logs au modèle, comblant l'incapacité du pur visuel à capturer les erreurs JS (doAction:378). - Approbation dédiée pour launch : ouvrir un navigateur est une action à risque ; launch passe par un type ask distinct
browser_action_launch, les autres actions (click/type/scroll) sont auto-approuvées dès lors que launch a été validé (launch flow:81). - Repli sur navigateur distant : en cas d'échec de connexion au navigateur distant, on retombe en mode local pour ne pas faire planter l'outil (
remote fallback:181-198). - Attente de navigation après click : un clic peut déclencher une navigation ; doAction écoute l'événement
requestlors du click pour détecter une activité réseau et, le cas échéant, appellewaitForNavigation(click:528-561). - Fermeture obligatoire avant de changer d'outil : le résultat contient un rappel « REMEMBER ... il faut close le navigateur avant d'utiliser un outil non browser_action », pour empêcher le modèle de basculer vers des modifications de fichiers pendant que le navigateur est ouvert (
result reminder:190-194).
Fichiers clés
class declaration:12—export class BrowserToolHandler implements IFullyManagedTool,name = ClineDefaultTool.BROWSER.handlePartialBlock:19— branche de streaming : launch passe par un askbrowser_action_launch, les autres actions se contentent de say les coordonnées/texte courants.execute:62— flux principal : validation de action → branche launch ou click/type/scroll/close → renvoyer screenshot + logs.launch flow:81-127— flux complet de launch : validation de l'url, approbation, hook PreToolUse,applyLatestBrowserSettingspour rafraîchir la session, puislaunchBrowser+navigateToUrl.click/type param check:130-145— si click manque coordinate ou type manque text, on signale l'erreur et on closeBrowser.action dispatch:163-179— switch qui distribue versbrowserSession.click/type/scrollDown/scrollUp/closeBrowser.result format:184-203— launch/click/type/scroll renvoient screenshot + logs, close renvoie un simple texte.error close:205-208— toute exception déclenchecloseBrowser, sans laisser de processus navigateur.launchBrowser:155— distant en priorité, repli local en cas d'échec, puisbrowser.newPage()pour ouvrir un nouvel onglet.doAction:378— installe les listeners console/pageerror, exécute l'action, attend 500 ms de silence, capture, nettoie les listeners.navigateToUrl:481—page.gotoavecdomcontentloaded + networkidle2, puiswaitTillHTMLStablesonde la taille du HTML.click:528— découpe les coordonnées,page.mouse.click, écoute request pour décider d'attendre la navigation.screenshot:441— webp en priorité, repli png en cas d'échec, et si tout échoue on lève une erreur et on envoie la télémétrie.
Flux de données
La branche launch est le flux le plus complet de BrowserToolHandler : validation de l'url → approbation → hook → rafraîchissement de browserSession → ouverture du navigateur → navigation → renvoi de la capture et des logs. Code clé :
// 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)
}Notez la ligne config.services.browserSession = await config.callbacks.applyLatestBrowserSettings() : elle ne se contente pas de rafraîchir les réglages, elle remplace la référence à browserSession détenue par ToolExecutor, de sorte que les appels suivants d'outils browser obtiennent bien la même nouvelle session (applyLatestBrowserSettings:125).
navigateToUrl passe en interne par doAction, comme toutes les actions autres que launch. doAction combine capture + logs console en un 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,
}Le handler emballe ce result dans formatResponse.toolResult et y ajoute le rappel « close le navigateur avant de revenir aux autres outils ».
Limites et échecs
- action invalide : à la complétion du block, si action n'est pas dans la liste
browserActions, on faitconsecutiveMistakeCount+++sayAndCreateMissingParamError+closeBrowser(invalid action:69-75). - launch sans url : launch exige url, son absence déclenche la même chose : mistake++ et closeBrowser (
missing url:82-87). - click sans coordinate / type sans text : paramètre manquant, on signale l'erreur et on closeBrowser pour ne pas laisser de session zombie (
param checks:131-145). - échec de connexion au navigateur distant : si
launchRemoteBrowserlève, on catch et on replie surlaunchLocalBrowser, avec télémétrie remote_browser_launch_error (remote fallback:181-198). - échec de capture : si webp échoue, on tente png, et si les deux échouent on lève « Failed to take screenshot. » avec télémétrie screenshot_error (
screenshot retry:448-466). - page non démarrée :
doActionvérifiethis.page; s'il n'existe pas, on lève une erreur « Browser is not launched ». Cela se produit généralement avec un outil non browser_action (qui n'a en fait pas le droit) ou après un close suivi d'un nouvel appel (page check:379-383). - toute exception ferme le navigateur : le try/catch de bas niveau du handler appelle
browserSession.closeBrowser(), garantissant qu'aucun processus Chrome ne reste à consommer des ressources (error close:205-208).
Résumé
BrowserToolHandler est une coquille fine : le vrai travail se fait dans BrowserSession. Le modèle choisit launch/click/type/scroll/close via le paramètre action, le handler valide les paramètres et gère l'approbation, délègue l'action aux appels Puppeteer de BrowserSession, puis renvoie capture et logs console au modèle. launch est approuvé séparément, les autres actions réutilisent la session ouverte ; toute erreur ferme le navigateur.
Pour aller plus loin :
- Outils de fichiers et de commande :
/cap-tools/file-ops - Web fetch/search :
/cap-tools/web - Chaîne de distribution des outils :
/agent-loop/task-class
Voir la documentation officielle : documentation Cline · README.