Skip to content

Outils de commande et de lecture : ExecuteCommand / ReadFile / SearchFiles / ListFiles

源码版本v4.0.10

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 consecutiveMistakeCount en 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 : fileReadCache ne 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 : formatFileContentWithLineNumbers préfixe chaque ligne de N | 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-56isLikelyLongRunningCommand + resolveCommandTimeoutSeconds, priorité au timeout fourni par le modèle.
  • ExecuteCommand class:58name = 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-190commandPermissionController.validateCommand + clineIgnoreController.validateCommand.
  • approval flow:223-279 — auto-approve exige !requiresApprovalPerLLM && autoApproveSafe ou, pour une commande risquée, autoApproveSafe && autoApproveAll.
  • cache clear:319-327 — après exécution de commande, on vide tout fileReadCache.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:142name = 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 — appelle extractFileContent (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 — appelle regexSearchFiles (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:100listFiles(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 :

typescript
// 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 :

typescript
// 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 :

typescript
// 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_PERMISSIONS refuse, on renvoie permissionDeniedError avec 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 extractFileContent est catchée en toolError("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 parseWorkspaceInlinePath lève, on renvoie Error resolving search path: ... et mistake++ (path resolution error:233-242).
  • list files hors limites : si listFiles lève ou le chemin est invalide, on renvoie Error listing files: ... ; en succès, didHitLimit est converti en invite par formatFilesList (list error:101-105).
  • sous-agent saute l'UI : les quatre handlers détectent isSubagentExecution et 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.