Outils de commande et de lecture : ExecuteCommand / ReadFile / SearchFiles / ListFiles
Responsabilités
Cette page décrit les quatre outils « de capacité » en lecture seule/exécution de Cline, tous enregistrés sous l'interface IFullyManagedTool et orchestrés par ToolExecutor :
- ExecuteCommandToolHandler (
ClineDefaultTool.BASH) : exécute une commande shell dans le terminal, avec timeout, multi-workspace et validation de permissions (class declaration:58). - ReadFileToolHandler (
ClineDefaultTool.FILE_READ) : lit un fichier, ajoute des numéros de ligne 1-based, supporte le slicing par start_line/end_line, et renvoie un image block pour les images (class declaration:142). - SearchFilesToolHandler (
ClineDefaultTool.SEARCH) : recherche regex basée sur ripgrep, supporte la recherche parallèle multi-workspace et le filtrage par file_pattern (class declaration:21). - ListFilesToolHandler (
ClineDefaultTool.LIST_FILES) : liste un répertoire, supporte la récursion, tronque au-delà de 200 entrées (class declaration:17).
Ces quatre outils partagent un même schéma d'interaction : handlePartialBlock pousse l'UI en avance pour que l'utilisateur voie « le modèle lit X », puis execute enchaîne validation (clineignore + chemin + paramètres) → approbation → hook PreToolUse → exécution réelle → télémétrie. Ils respectent tous config.isSubagentExecution : en exécution sous-agent, on saute une partie des pushs UI et des ask pour aller directement au travail.
La différence fondamentale avec WriteToFileToolHandler est « lecture seule ou effet de bord externe » — read/list/search ne modifient pas le système de fichiers ; execute_command modifie l'état mais via le terminal, hors de la vue diff, donc cette famille d'outils n'a pas de chemin de retour diff / userEdits.
Motivation de conception
- Règle uniforme du compteur d'erreurs : chaque outil incrémente
consecutiveMistakeCounten cas de paramètre manquant ou de correspondance clineignore (hit), et le remet à 0 après une opération réussie, afin que le quota d'erreurs consécutives en mode YOLO s'accumule entre outils (reset counter:377). - Timeout catégorisé pour les commandes : execute_command attribue 300 s aux commandes long-running courantes (npm install, cargo build, pytest, etc.) et 30 s aux autres, pour ne pas que les commandes courtes soient pénalisées par un long timeout (
LONG_RUNNING patterns:22-34). - Double contrôle de permissions : whitelist segment-level via la variable d'environnement
CLINE_COMMAND_PERMISSIONS+ validation clineignore des chemins, deux couches qui se rejettent indépendamment (permission checks:162-190). - Cache de déduplication pour la lecture :
fileReadCachene stocke que des métadonnées (readCount + mtime + imageBlock), pas le contenu ; sur hit avec mtime inchangé, on renvoie un prompt « déjà lu » pour empêcher le modèle de gaspiller des tokens à relire le même fichier (dedup cache:298-314). - Affichage avec numéros de ligne :
formatFileContentWithLineNumberspréfixe chaque ligne deN |et ajoute une invite de continuation, pour que le modèle reprenne la lecture précisément via start_line (formatFileContentWithLineNumbers:79). - Recherche parallèle multi-workspace : en mode multi-workspace, SearchFilesToolHandler lance tous les searchPaths en parallèle via
Promise.all, puis regroupe par nom de workspace (parallel search:279-285). - ListFiles plafonné à 200 : listFiles reçoit un troisième paramètre limit=200 ; au-delà, didHitLimit=true et formatFilesList ajoute une invite à la fin pour que le modèle affine avec un path plus précis (
listFiles call:100).
Fichiers clés
LONG_RUNNING patterns:22-34— tableau d'expressions régulières pour les commandes longues : npm/cargo/pytest/docker build/torchrun, etc.timeout resolution:36-56—isLikelyLongRunningCommand+resolveCommandTimeoutSeconds, priorité au timeout fourni par le modèle.ExecuteCommand class:58—name = ClineDefaultTool.BASH; handlePartialBlock attend le block complet en auto-approve avant de say.workspace hint:135-159— parsing du préfixe@workspace:command, change executionDir vers la racine du workspace correspondant.permission checks:162-190—commandPermissionController.validateCommand+clineIgnoreController.validateCommand.approval flow:223-279— auto-approve exige!requiresApprovalPerLLM && autoApproveSafeou, pour une commande risquée,autoApproveSafe && autoApproveAll.cache clear:319-327— après exécution de commande, on vide toutfileReadCache.clear(), car sed/git checkout/mv peuvent modifier des fichiers de façon imprévisible.formatFileContentWithLineNumbers:79— slicing, ajout des numéros de ligne, et invite « Showing lines X-Y of Z ».ReadFile class:142—name = ClineDefaultTool.FILE_READ.dedup cache:298-356— comparaison mtime + incrémentation readCount ; à partir du 3e passage on renvoie un avertissement DUPLICATE READ.extract + cache:359-373— appelleextractFileContent(image → imageBlock) ; en cas d'échec, tool error plutôt qu'une exception remontée.determineSearchPaths:35— en multi-workspace, déploie les chemins de recherche selon hint ou sur tous les workspaces.executeSearch:74— appelleregexSearchFiles(ripgrep) et filtre les résultats via clineIgnoreController.formatSearchResults:120— en multi-workspace, regroupe par nom de workspace ; en mono-workspace, renvoie directement.listFiles call:100—listFiles(absolutePath, recursive, 200)renvoie [files, didHitLimit].
Flux de données
ExecuteCommand est le plus complexe des quatre ; son cœur est la porte d'approbation et la résolution du timeout. Les champs requires_approval + command fournis par le modèle décident quel chemin d'approbation emprunter :
// apps/vscode/src/core/task/tools/handlers/ExecuteCommandToolHandler.ts
timeoutSeconds = resolveCommandTimeoutSeconds(
command,
timeoutParam,
config.yoloModeToggled || config.vscodeTerminalExecutionMode === "backgroundExec",
)
// ...
if (
config.isSubagentExecution ||
(!requiresApprovalPerLLM && autoApproveSafe) ||
(requiresApprovalPerLLM && autoApproveSafe && autoApproveAll)
) {
// Auto-approve flow
await config.callbacks.removeLastPartialMessageIfExistsWithType("ask", "command")
await config.callbacks.say("command", actualCommand, undefined, undefined, false)
didAutoApprove = true
} else {
// Manual approval flow
const didApprove = await ToolResultUtils.askApprovalAndPushFeedback(
"command",
actualCommand + `${autoApproveSafe && requiresApprovalPerLLM ? COMMAND_REQ_APP_STRING : ""}`,
config,
)
if (!didApprove) {
return formatResponse.toolDenied()
}
}autoApprover.shouldAutoApproveTool renvoie le couple [autoApproveSafe, autoApproveAll] : le premier signifie « auto-approuve les commandes safe », le second « auto-approuve toutes les commandes ». Quand le modèle dit risqué (requires_approval=true) et que seul safe est auto-approuvé, on passe en approbation manuelle et on ajoute à la commande la chaîne de forte approbation (auto-approve logic:223-227). Une fois approuvé : hook PreToolUse, puis minuteur de notification de 30 s (uniquement en auto-approve + notifications activées), et enfin executeCommandTool(finalCommand, timeoutSeconds) s'exécute dans le terminal. À la fin, tout fileReadCache est vidé.
Le cœur de ReadFileToolHandler, c'est le cache de déduplication :
// apps/vscode/src/core/task/tools/handlers/ReadFileToolHandler.ts
const cacheKey = absolutePath.toLowerCase()
const cached = config.taskState.fileReadCache.get(cacheKey)
if (cached) {
try {
const stat = await import("node:fs/promises").then((fs) => fs.stat(absolutePath))
if (stat.mtimeMs !== cached.mtime) {
config.taskState.fileReadCache.delete(cacheKey)
}
} catch {
config.taskState.fileReadCache.delete(cacheKey)
}
}Si mtime a changé (l'utilisateur a édité le fichier à la main), on évicte, sinon on réutilise le cache. Celui-ci ne stocke pas le contenu pour économiser la mémoire ; sur hit, on relit depuis le disque. Au-delà de 3 lectures, on renvoie un fort avertissement DUPLICATE READ. Sur une nouvelle lecture réussie, extractFileContent récupère le contenu (les fichiers image renvoient un imageBlock poussé dans userMessageContent) et on stocke mtime + readCount=1 dans le cache.
SearchFiles et ListFiles font tous deux parsing de chemin + clineignore + approbation + exécution + formatage, avec une structure de code similaire. SearchFiles ajoute le parallélisme multi-workspace :
// apps/vscode/src/core/task/tools/handlers/SearchFilesToolHandler.ts
const searchPromises = searchPaths.map(({ absolutePath, workspaceName, workspaceRoot }) =>
this.executeSearch(config, absolutePath, workspaceName, workspaceRoot, regex, filePattern),
)
const searchResults = await Promise.all(searchPromises)
const results = this.formatSearchResults(config, searchResults, searchPaths)regexSearchFiles appelle ripgrep en sous-couche ; les résultats sont regroupés par nom de workspace.
Limites et échecs
- paramètre manquant : execute_command sans command / requires_approval, read/search/list sans path, search sans regex — tous font
consecutiveMistakeCount+++sayAndCreateMissingParamError(missing params:100-112). - commande refusée : si
CLINE_COMMAND_PERMISSIONSrefuse, on renvoiepermissionDeniedErroravec failedSegment ou matchedPattern pour donner au modèle une information exploitable (permission denied:162-181). - notification commande longue : en auto-approve + notifications activées, une notification système s'affiche après 30 s « Command is still running », pour éviter que l'utilisateur croie le système bloqué (
timeout notification:294-303). - fichier à lire absent : une erreur
extractFileContentest catchée entoolError("Error reading file: ...")sans remonter à Task, afin que le modèle voie l'erreur et corrige le chemin (read error:361-373). - mtime du cache incohérent : si stat échoue ou mtime a changé, on évicte le cache pour garantir une lecture fraîche au prochain appel (
mtime check:304-313). - échec de résolution du path de search : si
parseWorkspaceInlinePathlève, on renvoieError resolving search path: ...et mistake++ (path resolution error:233-242). - list files hors limites : si
listFileslève ou le chemin est invalide, on renvoieError listing files: ...; en succès, didHitLimit est converti en invite parformatFilesList(list error:101-105). - sous-agent saute l'UI : les quatre handlers détectent
isSubagentExecutionet sautent les flux partial say et ask pour directement exécuter et retourner (subagent skip:155-157).
Résumé
Ces quatre handlers sont les « yeux et mains » de Cline : ExecuteCommand lance du shell, ReadFile lit le disque, SearchFiles lance ripgrep, ListFiles énumère les répertoires. Ils partagent le squelette « validation → approbation → hook → exécution → télémétrie », et diffèrent sur les détails d'exécution : execute_command a un timeout catégorisé et une double porte d'approbation ; read_file un cache de déduplication par mtime ; search supporte le parallélisme multi-workspace ; list plafonne à 200. Pour tous, consecutiveMistakeCount est remis à 0 en cas de succès et s'accumule en cas d'échec jusqu'au plafond YOLO.
Pour aller plus loin :
- Outils d'écriture disque :
/edit-tools/write-to-file - Patch multi-fichiers :
/edit-tools/apply-patch - Outil navigateur :
/cap-tools/browser - Web fetch/search :
/cap-tools/web
Voir la documentation officielle : documentation Cline · README.