diff --git a/CLAUDE.md b/CLAUDE.md index 6686729..f182e18 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -146,6 +146,10 @@ explicite (D9). Par défaut `false`. **`LOGS_PATH`** accepte le placeholder `{host}`, substitué par le host du profil actif à chaque appel. +**`MAX_LOG_SEARCH_CHARS`** (défaut 25 000) : plafond en caractères de la réponse +de `search_logs` — au-delà, des résultats entiers sont écartés et signalés +(`truncated`, D24). + **Au runtime.** `profile-manager` est un singleton d'état global. Les services s'abonnent via `onSwitch()` pour invalider ce qui dépend du tenant : diff --git a/src/tools/log-tools.js b/src/tools/log-tools.js index e265946..c151347 100644 --- a/src/tools/log-tools.js +++ b/src/tools/log-tools.js @@ -182,22 +182,39 @@ async function searchLogs(args) { const result = await logService.searchLogs(keyword, max_results, context_lines); + // Garde-fou de taille (L3.1) : max_results borne le nombre de résultats, + // pas le volume — les context_lines multiplient la taille (55 954 chars + // mesurés avec les seuls défauts, rejetés par le client MCP). Au-delà du + // plafond on écarte des résultats ENTIERS (jamais coupés au milieu de + // leur contexte) et on le signale : truncated + omitted + hint. + const cap = parseInt(process.env.MAX_LOG_SEARCH_CHARS) || 25000; + + const buildText = (kept) => { + const omitted = result.results.length - kept.length; + const payload = { + success: true, + keyword: result.keyword, + totalResults: result.totalResults, + returned: kept.length, + }; + if (omitted > 0) { + payload.truncated = true; + payload.omitted = omitted; + payload.hint = `Plafond de taille de réponse atteint (${cap} caractères) : ${kept.length} résultat(s) renvoyé(s) sur ${result.totalResults}, ${omitted} écarté(s). Affinez le keyword, réduisez context_lines ou baissez max_results.`; + } + payload.results = kept; + return JSON.stringify(payload, null, 2); + }; + + let kept = result.results.slice(); + let text = buildText(kept); + while (text.length > cap && kept.length > 0) { + kept.pop(); + text = buildText(kept); + } + return { - content: [ - { - type: 'text', - text: JSON.stringify( - { - success: true, - keyword: result.keyword, - totalResults: result.totalResults, - results: result.results, - }, - null, - 2 - ), - }, - ], + content: [{ type: 'text', text }], }; } catch (err) { return {