mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-17 20:26:35 +02:00
La façade tient la session Chrome des outils browser_* (src/browser/, port de computer_cdp.go, computer_use.go, browser_grid.go et cu_autoshot.go) : lancement (profil temporaire, mêmes arguments, tué avec la façade), client CDP multiplexé sur websocket, attente du chargement et du calme réseau, scripts injectés copiés à l'octet près, les neuf outils aux textes du Go (snapshot dédoublonné, recherche, clics, saisie et listes déroulantes, touches, défilement, navigation et ses erreurs), capture rangée propre et quadrillée pour le modèle (mêmes pixels), aperçus JPEG en anneau servis par /api/chat/cushot. Un seul navigateur : quand la façade tient la conversation, le Go lui passe les outils de ses sous-agents et tâches IA, ses aperçus et la fermeture (POST /internal/facade/v1/browser) ; façade injoignable, le navigateur du Go prend le relais. Couper l'interrupteur ferme les deux. Parité : journal de 42 étapes tiré du Go contre un faux Chrome (script + faux serveur CDP qui note chaque commande, scripts résumés par leur empreinte, pixels de la grille), rejoué par tests/browser.rs ; un vrai Chromium de bout en bout avec LOKI_CU_SMOKE=1 ; un tour de la façade qui ouvre une page (tests/lot6.rs) ; TestFacadeNavigateur côté Go. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
96 lines
2.8 KiB
Go
96 lines
2.8 KiB
Go
package loki
|
|
|
|
// cu_autoshot.go — aperçu EN DIRECT du navigateur piloté (computer use). Après
|
|
// chaque action browser_*, on capture la page pour que l'UTILISATEUR voie ce que
|
|
// fait l'IA. C'est de l'interface seulement :
|
|
// - jamais envoyé au modèle (zéro token, vision non requise) ;
|
|
// - jamais écrit sur le disque : un petit anneau en mémoire vive ;
|
|
// - retiré du journal en fin de tour (compactLogLocked), et l'UI efface la carte
|
|
// à la fin du tour. Une capture à GARDER passe toujours par
|
|
// browser_screenshot, qui l'enregistre dans le dossier de travail.
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"image"
|
|
"image/jpeg"
|
|
_ "image/png"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
const cuShotRing = 8 // captures gardées en RAM (l'UI n'affiche que la dernière)
|
|
|
|
var (
|
|
cuShotMu sync.Mutex
|
|
cuShotData = map[string][]byte{}
|
|
cuShotIDs []string
|
|
)
|
|
|
|
// cuAutoShotTool : outils après lesquels on capture (ceux qui changent la page).
|
|
func cuAutoShotTool(name string) bool {
|
|
switch name {
|
|
case "browser_open", "browser_click", "browser_click_xy", "browser_type", "browser_key", "browser_scroll", "browser_find":
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func cuLog(format string, a ...any) { fmt.Fprintf(os.Stderr, "[cu] "+format+"\n", a...) }
|
|
|
|
// cuAutoShot capture la page en pleine résolution (JPEG de bonne qualité) et
|
|
// renvoie l'identifiant de la capture en mémoire ("" si impossible : jamais
|
|
// bloquant pour l'outil).
|
|
func cuAutoShot() string {
|
|
if id, ok := facadeBrowserShot(); ok {
|
|
return id
|
|
}
|
|
s, err := cdpGet()
|
|
if err != nil {
|
|
cuLog("aperçu : navigateur indisponible : %v", err)
|
|
return ""
|
|
}
|
|
raw, err := s.screenshot()
|
|
if err != nil {
|
|
cuLog("aperçu : capture impossible : %v", err)
|
|
return ""
|
|
}
|
|
img, _, err := image.Decode(bytes.NewReader(raw))
|
|
if err != nil {
|
|
cuLog("aperçu : image illisible (%d octets) : %v", len(raw), err)
|
|
return ""
|
|
}
|
|
var buf bytes.Buffer
|
|
if err := jpeg.Encode(&buf, img, &jpeg.Options{Quality: 90}); err != nil {
|
|
return ""
|
|
}
|
|
id := fmt.Sprintf("%x", time.Now().UnixNano())
|
|
cuShotMu.Lock()
|
|
cuShotData[id] = buf.Bytes()
|
|
cuShotIDs = append(cuShotIDs, id)
|
|
for len(cuShotIDs) > cuShotRing {
|
|
delete(cuShotData, cuShotIDs[0])
|
|
cuShotIDs = cuShotIDs[1:]
|
|
}
|
|
cuShotMu.Unlock()
|
|
return id
|
|
}
|
|
|
|
// handleCUShot (GET ?id=) : une capture en base64 (JSON, pour traverser aussi le
|
|
// tunnel chiffré du relais). 404 si elle est déjà sortie de l'anneau.
|
|
func handleCUShot(w http.ResponseWriter, r *http.Request) {
|
|
id := strings.TrimSpace(r.URL.Query().Get("id"))
|
|
cuShotMu.Lock()
|
|
b := cuShotData[id]
|
|
cuShotMu.Unlock()
|
|
if b == nil {
|
|
sendJSON(w, 404, map[string]any{"ok": false, "error": "capture expirée"})
|
|
return
|
|
}
|
|
sendJSON(w, 200, map[string]any{"ok": true, "mime": "image/jpeg", "data": base64.StdEncoding.EncodeToString(b)})
|
|
}
|