Mode code : la capture d'écran sans accès internet, bornée au local

Le modèle cherchait à vérifier visuellement son travail, ne trouvait pas
web_screenshot (offert seulement avec l'accès internet), en concluait
« pas de Chromium local » et partait installer puppeteer — alors qu'un
Chromium headless est dans l'image depuis toujours.

L'outil est donc offert aussi au mode code sans internet, mais borné aux
adresses locales (localhost, 127.0.0.1, ::1, *.localhost) : photographier
son propre serveur de dev est une vérification locale, pas une sortie sur
le web — l'interrupteur d'accès internet garde tout son sens. Le prompt
du builder dit explicitement que le navigateur est déjà là et qu'il ne
faut installer aucun paquet de navigateur.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Opus 5 committed 2026-08-19 08:08:41 +02:00
1 parent 608e7f7038
commit 0430aa2281
3 files changed
+37 -9

No files matched your search

+1 -1
View File
@@ -4,7 +4,7 @@ Method — in this order:
1. If the task has no acceptance criteria yet, set them FIRST with the criteria tool (action=add): 2-6 short, testable statements of what DONE means. For a trivial task (one obvious change), skip criteria and just do it. 1. If the task has no acceptance criteria yet, set them FIRST with the criteria tool (action=add): 2-6 short, testable statements of what DONE means. For a trivial task (one obvious change), skip criteria and just do it.
2. Explore before you change: read the relevant files (read, grep, glob, git_status). Never edit a file you have not read. 2. Explore before you change: read the relevant files (read, grep, glob, git_status). Never edit a file you have not read.
3. Implement with edit (small patches) or write (new files). Match the style of the surrounding code. 3. Implement with edit (small patches) or write (new files). Match the style of the surrounding code.
4. Prove it: run the build/tests with bash. A change that was never run is not done. 4. Prove it: run the build/tests with bash. A change that was never run is not done. For a web page or UI, start the dev server with bash_bg then use web_screenshot on its localhost URL — a headless Chromium is ALREADY installed, so NEVER install puppeteer, playwright or a browser.
5. Report briefly what changed and how you verified it. 5. Report briefly what changed and how you verified it.
Rules: Rules:
+25 -1
View File
@@ -20,6 +20,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
neturl "net/url"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@@ -242,7 +243,7 @@ func safeSlug(s string) string {
return s return s
} }
func toolWebScreenshot(args map[string]any) string { func toolWebScreenshot(args map[string]any, caps Caps) string {
bin := playwrightBin() bin := playwrightBin()
if bin == "" { if bin == "" {
return "[erreur] Playwright n'est pas installé dans cette image (bâtie avec PLAYWRIGHT=0)." return "[erreur] Playwright n'est pas installé dans cette image (bâtie avec PLAYWRIGHT=0)."
@@ -255,6 +256,13 @@ func toolWebScreenshot(args map[string]any) string {
if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") { if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
return "[erreur] url invalide : elle doit commencer par http:// ou https://" return "[erreur] url invalide : elle doit commencer par http:// ou https://"
} }
// Accès internet coupé : la capture reste offerte au MODE CODE, mais bornée
// aux adresses locales — voir sa propre page de dev, pas le web. Sans cette
// borne, l'outil rouvrirait par la fenêtre ce que l'interrupteur ferme.
if !caps.Internet && !isLocalURL(url) {
return "[refusé] L'accès internet est coupé : la capture ne marche que sur une adresse locale " +
"(localhost / 127.0.0.1), par exemple ton serveur de dev. Active l'accès internet pour le web."
}
// width n'est plus déclaré dans le schéma (budget de préambule), mais reste // width n'est plus déclaré dans le schéma (budget de préambule), mais reste
// honoré s'il arrive quand même : un modèle qui l'invente obtient le // honoré s'il arrive quand même : un modèle qui l'invente obtient le
// comportement attendu plutôt qu'un paramètre ignoré en silence. // comportement attendu plutôt qu'un paramètre ignoré en silence.
@@ -397,3 +405,19 @@ func lastLines(s string, n int) string {
} }
return strings.Join(keep, " · ") return strings.Join(keep, " · ")
} }
// isLocalURL : l'adresse désigne-t-elle cette machine ? Sert de borne quand
// l'accès internet est coupé mais que le mode code doit pouvoir photographier
// son propre serveur de dev.
func isLocalURL(raw string) bool {
u, err := neturl.Parse(raw)
if err != nil {
return false
}
host := u.Hostname()
switch host {
case "localhost", "127.0.0.1", "::1", "0.0.0.0":
return true
}
return strings.HasSuffix(host, ".localhost")
}
+11 -7
View File
@@ -296,12 +296,16 @@ func EnabledTools(caps Caps) []Tool {
// donc prompt et outils restent cohérents — pas de web_search halluciné. // donc prompt et outils restent cohérents — pas de web_search halluciné.
if caps.Agent && caps.Internet { if caps.Agent && caps.Internet {
tools = append(tools, webSearchTool(), webOpenTool(), webReadTool(), webGrepTool()) tools = append(tools, webSearchTool(), webOpenTool(), webReadTool(), webGrepTool())
// Capture d'écran : seulement si Playwright est réellement présent dans }
// l'image. Annoncer un outil absent enverrait le modèle en boucle de // Capture d'écran : seulement si Playwright est réellement présent dans
// réessai sur un échec systématique. // l'image (annoncer un outil absent enverrait le modèle en boucle de
if screenshotAvailable() { // réessai). Offerte aussi SANS accès internet quand le mode code est actif :
tools = append(tools, webScreenshotTool()) // photographier son propre serveur de dev est une vérification locale, pas
} // une sortie sur le web — et sans elle, le modèle croyait n'avoir aucun
// navigateur et perdait des minutes à installer puppeteer. La borne aux
// adresses locales vit dans toolWebScreenshot.
if caps.Agent && (caps.Internet || caps.Code) && screenshotAvailable() {
tools = append(tools, webScreenshotTool())
} }
// Outils MCP : serveurs tiers configurés par le propriétaire de la machine. // Outils MCP : serveurs tiers configurés par le propriétaire de la machine.
// Comme bash, ils exécutent du code arbitraire côté hôte → réservés au mode // Comme bash, ils exécutent du code arbitraire côté hôte → réservés au mode
@@ -1195,7 +1199,7 @@ func runChat(ctx context.Context, messages []Message, temperature float64, caps
case "web_grep": case "web_grep":
result = capWebOutput(toolWebGrep(args)) result = capWebOutput(toolWebGrep(args))
case "web_screenshot": case "web_screenshot":
result = toolWebScreenshot(args) result = toolWebScreenshot(args, caps)
default: default:
if isMCPTool(tc.Function.Name) { if isMCPTool(tc.Function.Name) {
result = mcpCall(tc.Function.Name, args) result = mcpCall(tc.Function.Name, args)