Skip to content

ToolValidator et autoApprove : les deux portes d'entrée avant exécution

源码版本v4.0.10

Responsabilités

Avant qu'un outil ne s'exécute réellement, deux contrôles indépendants interviennent : ToolValidator gère la validité des paramètres et des chemins, tandis que AutoApprove décide s'il faut afficher une boîte d'approbation. Aucun des deux n'exécute l'outil : ils tranchent uniquement « peut-on lancer » et « faut-il interroger l'utilisateur ».

ToolValidator est une classe utilitaire très fine, mise à disposition des handlers. Elle expose deux méthodes : assertRequiredParams vérifie la présence des paramètres obligatoires, et checkClineIgnorePath vérifie si le chemin est bloqué par .clineignore (assertRequiredParams:17). Sans effet de bord, elle renvoie simplement un ValidationResult et laisse au handler le soin de décider quoi en faire.

AutoApprove est un peu plus riche : c'est un champ privé de ToolExecutor, injecté dans TaskConfig via asToolConfig (autoApprover init:126). Le handler l'appelle indirectement via config.callbacks.shouldAutoApproveTool ou config.callbacks.shouldAutoApproveToolWithPath (callback wiring:181). Il lit trois commutateurs globaux — yoloModeToggled, autoApproveAllToggled, autoApprovalSettings — et, selon que le chemin se trouve ou non dans le workspace, renvoie un résultat à trois niveaux : « approbation automatique locale », « approbation automatique externe », « pas d'approbation ».

Motivation de conception

  • Séparation entre Validator et AutoApprove : contrôle de validité et approbation sont deux préoccupations orthogonales — un paramètre valide sans approbation utilisateur, ou un paramètre invalide déjà approuvé, peuvent tout à fait se produire. Les distinguer en deux objets permet au handler de ne pas court-circuiter l'approbation sur les chemins en erreur, et de continuer à intercepter les paramètres invalides même après approbation.
  • Le Validator observe sans modifier : il renvoie un ValidationResult au lieu de lever une erreur, et le handler décide s'il pousse un résultat d'erreur ou s'il propose un chemin dégradé (ValidationResult:4). Ainsi, plusieurs handlers peuvent partager un même mode d'affichage des erreurs.
  • Les trois niveaux de commutateurs d'AutoApprove : yolo ouvre tout, autoApproveAll est un cran plus restrictif, autoApprovalSettings est le plus fin (distinction local/externe, commandes sûres/toutes les commandes) (shouldAutoApproveTool:42). La priorité va du plus permissif au plus strict, le premier activé l'emporte.
  • Classification des chemins pour les accès externes : pour les outils de lecture/écriture, le résultat de l'auto-approve est un tuple [local, external] (tuple result:96). local régit les fichiers dans le workspace, external ceux à l'extérieur. Par défaut, l'extérieur est plus strict afin d'éviter de modifier par erreur des fichiers système.
  • Les infos du workspace mises en cache à la portée de la task : getWorkspaceInfo est calculé une fois puis caché dans workspacePathsCache et isMultiRootScenarioCache (getWorkspaceInfo:23). Pendant le cycle de vie d'une task, les racines du workspace sont essentiellement stables ; le cache évite des appels IPC répétés.

Fichiers clés

Flux de données

Une fois le handler en possession du block, le flux type est : Validator sur les paramètres d'abord, puis AutoApprove pour déterminer le chemin UI. Voici l'appel effectif opéré par WriteToFileToolHandler en phase de partial block :

typescript
// apps/vscode/src/core/task/tools/handlers/WriteToFileToolHandler.ts
// Handle auto-approval vs manual approval for partial
if (await uiHelpers.shouldAutoApproveToolWithPath(block.name, relPath)) {
    await uiHelpers.removeLastPartialMessageIfExistsWithType("ask", "tool") // in case the user changes auto-approval settings mid stream
    await uiHelpers.say("tool", partialMessage, undefined, undefined, block.partial)
} else {
    await uiHelpers.removeLastPartialMessageIfExistsWithType("say", "tool")
    await uiHelpers.ask("tool", partialMessage, block.partial).catch(() => {})
}

shouldAutoApproveToolWithPath consulte d'abord les deux commutateurs globaux YOLO et autoApproveAll : si l'un est vrai, elle renvoie true directement (yolo short circuit:126). Ensuite, elle détermine si le chemin est dans le workspace et combine ce résultat avec le tuple renvoyé par shouldAutoApproveTool pour produire la décision finale :

typescript
// apps/vscode/src/core/task/tools/autoApprove.ts
const autoApproveResult = this.shouldAutoApproveTool(blockname)
const [autoApproveLocal, autoApproveExternal] = Array.isArray(autoApproveResult)
    ? autoApproveResult
    : [autoApproveResult, false]

if ((isLocalRead && autoApproveLocal) || (!isLocalRead && autoApproveLocal && autoApproveExternal)) {
    return true
}
return false

Ici, autoApproveExternal n'est testé que si autoApproveLocal est vrai : l'approbation automatique externe est donc implicitement incluse dans l'approbation automatique locale. C'est la posture de sécurité par défaut de Cline : une opération externe est toujours un cran plus stricte qu'une opération locale.

Limites et échecs

  • La chaîne vide compte comme valeur manquante : assertRequiredParams utilise String(val).trim() === "", donc une chaîne vide ou composée d'espaces est considérée comme manquante (empty check:21). En revanche, le champ content autorise la chaîne vide (création d'un fichier vide) ; le handler le teste donc lui-même via == null, sans passer par le Validator (content null check:42).
  • Sans chemin, refus par défaut : quand shouldAutoApproveToolWithPath reçoit un chemin undefined, elle positionne directement isLocalRead = false (no path default:153). Côté sécurité, en l'absence de chemin, on traite l'opération comme externe.
  • Le cache suppose un workspace invariant : le commentaire le dit explicitement : « assumes that the task has a fixed set of workspace roots » (cache assumption:11). Si l'utilisateur ajoute une nouvelle racine en plein milieu d'une task, celle-ci continuera de travailler sur le cache périmé ; il faudra attendre la task suivante pour qu'il soit rafraîchi.
  • YOLO ne couvre pas tous les outils : le switch YOLO ne liste que read/write/bash/browser/web/MCP, etc., et omet ASK, ATTEMPT, NEW_TASK (yolo switch list:44). Ces outils tombent par défaut sur return false et nécessitent une approbation normale.
  • Le tuple YOLO ne vaut que pour file/bash : le tuple [true, true] n'est renvoyé que pour read/write/bash/subagents (yolo tuple:55). browser/web/MCP reçoivent la valeur scalaire true, ce qui signifie qu'en mode YOLO ils ne distinguent pas local et externe.
  • Le Validator n'est pas utilisé par l'ancien ToolExecutor : le commentaire de classe le précise : « The legacy ToolExecutor switch remains unchanged and does not depend on this » (legacy note:8). Sur l'ancien chemin, les contrôles de paramètres sont éparpillés dans les différents case ; le Validator ne sert que les nouveaux handlers.

Résumé

Le Validator est le contrôle dur des paramètres et chemins : en cas d'échec, il renvoie une erreur pour que le modèle réessaie. AutoApprove est un commutateur souple piloté par les préférences utilisateur, qui tranche entre auto-approval et affichage d'une boîte de dialogue. Ensemble, ils font office de gardiens avant l'exécution de l'outil : ce n'est qu'après les avoir consultés que le handler décide de la suite. Pour le mécanisme d'enregistrement des handlers, voir /tools/handlers-overview ; pour la logique de routage elle-même, voir /tools/coordinator.

Voir la documentation officielle : documentation Cline · README.