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.
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.
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.
Rules:
+25 -1
View File
@@ -20,6 +20,7 @@ import (
"encoding/json"
"fmt"
"net/http"
neturl "net/url"
"os"
"os/exec"
"path/filepath"
@@ -242,7 +243,7 @@ func safeSlug(s string) string {
return s
}
func toolWebScreenshot(args map[string]any) string {
func toolWebScreenshot(args map[string]any, caps Caps) string {
bin := playwrightBin()
if bin == "" {
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://") {
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
// 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.
@@ -397,3 +405,19 @@ func lastLines(s string, n int) string {
}
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é.
if caps.Agent && caps.Internet {
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
// réessai sur un échec systématique.
if screenshotAvailable() {
tools = append(tools, webScreenshotTool())
}
}
// 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
// réessai). Offerte aussi SANS accès internet quand le mode code est actif :
// 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.
// 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":
result = capWebOutput(toolWebGrep(args))
case "web_screenshot":
result = toolWebScreenshot(args)
result = toolWebScreenshot(args, caps)
default:
if isMCPTool(tc.Function.Name) {
result = mcpCall(tc.Function.Name, args)