Navigateur piloté, images redressées : reprises d'AJEAN

L'amont (AJEAN 0.15) donne à l'IA le contrôle d'un navigateur ; Loki
embarquait déjà un Chromium pour `web_screenshot` sans jamais le piloter.

Contrôle du navigateur (computer_use.go, computer_cdp.go, browser_grid.go,
portés d'AJEAN) : l'IA ouvre une page en CDP, en reçoit les éléments
interactifs NUMÉROTÉS et agit par numéro (browser_open/snapshot/find/click/
type/key/scroll). Aucune vision requise — un petit modèle texte s'en sort.
Avec un projecteur chargé s'ajoutent browser_screenshot (image quadrillée)
et browser_click_xy. Interrupteur dédié (Réglages, `loki computer`,
/api/computer), sous le mode agent : cliquer et taper dans une page sont des
actions réelles, même niveau de confiance que bash.

Adaptation au conteneur : chromePath cherche D'ABORD le Chromium de
Playwright (PLAYWRIGHT_BROWSERS_PATH, /opt/pw-browsers), le seul navigateur
de l'image — sinon la fonctionnalité se déclarait absente là où le
navigateur est présent. LOKI_CHROME force un chemin, LOKI_CU_HEADFUL ouvre
une vraie fenêtre.

Préparation des images (web_upload_orient.go, porté d'AJEAN) : l'orientation
EXIF est cuite dans les pixels — le projecteur l'ignore et voyait les photos
de téléphone couchées — et le grand côté ramené sous 1568 px. Appliquée aux
pièces jointes, à see_image et aux captures.

Dédup des appels d'outils : bash, bash_bg, bash_tail, see_image et les
browser_* ne sont plus court-circuités sur un appel identique. Relancer la
même commande après avoir modifié un fichier est légitime, et un outil qui
porte une image dans un message à part renvoyait « [déjà fait] » SANS
l'image — le modèle tournait en boucle.

Mode code : le builder ne publie plus de lui-même (pas de commit/push/reset/
rebase ni de redémarrage de service sans demande explicite), reprise de la
leçon d'AJEAN 0.15.4 ; l'inspection en lecture seule reste encouragée.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K6CAgoLJzufeA8rZTSpSpY
This commit is contained in:
Claude committed 2026-09-22 13:19:37 +00:00
1 parent b5904907e8
commit 00b2350fb9
21 files changed
+2503 -19

No files matched your search

+12
View File
@@ -434,6 +434,18 @@ document.documentElement.setAttribute('data-side',localStorage.getItem('loki-sid
</div>
</div>
</div>
<!-- Pilotage de navigateur (computer use) : axe de capacité à part, sous le
mode agent comme l'accès internet — un outil qui clique et tape dans une
page fait des actions RÉELLES, même niveau de confiance que bash. -->
<div id="computer-block" class="gated-group">
<div class="subhead">Contrôle du navigateur<span id="computer-badge" class="sbadge"></span><span class="help" tabindex="0" onclick="event.preventDefault();event.stopPropagation();this.classList.toggle('open')">?<span class="tip">Donne à l'IA le contrôle d'un <b>navigateur</b> dans le conteneur (outils <b>browser_open / browser_find / browser_click / browser_type / browser_key / browser_scroll</b>). L'IA lit les éléments <b>numérotés</b> de la page et agit par numéro — aucune vision requise, ça marche avec de petits modèles. <code>browser_screenshot</code> s'ajoute si la vision est active. Utilise le Chromium de Playwright embarqué dans l'image ; à défaut, <code>LOKI_CHROME=&lt;chemin&gt;</code>. Actif seulement si le <b>mode agent</b> est actif.</span></span></div>
<div class="slist">
<label class="switchrow"><span class="switch"><input type="checkbox" id="computer-toggle" onchange="toggleComputer()"><span class="slider"></span></span> laisser l'IA piloter un navigateur</label>
<div class="rowbody">
<div id="computer-status" class="muted" style="font-size:11px;margin-top:4px"></div>
</div>
</div>
</div>
<div id="mcp-block" class="gated-group">
<div class="subhead">Serveurs MCP<span id="mcp-badge" class="sbadge"></span><span class="help" tabindex="0" onclick="event.preventDefault();event.stopPropagation();this.classList.toggle('open')">?<span class="tip">Connecte des <b>serveurs MCP</b> (Model Context Protocol) pour donner de nouveaux outils à l'IA : accès fichiers, bases de données, APIs métier… <b>stdio</b> = programme lancé localement (npx/uvx/binaire) · <b>http</b> = serveur distant. ⚠ un serveur stdio exécute du code sur cette machine, au même titre que le shell.</span></span></div>
<div class="slist">
+28 -2
View File
@@ -133,7 +133,7 @@ async function loadAgent(){
// les outils mem_* ni les outils web ne sont fournis (voir globalCaps côté Go). On
// grise donc ces blocs quand l'agent est off pour que l'UI ne mente pas.
function setAgentGate(on){
['mem-block','net-block','mcp-block','param-block'].forEach(id=>{
['mem-block','net-block','computer-block','mcp-block','param-block'].forEach(id=>{
const el=document.getElementById(id); if(el) el.classList.toggle('gated', !on);
});
}
@@ -242,6 +242,32 @@ function renderInternet(s){
else { st.innerHTML='⚠ serveur injoignable — les outils web ne seront pas proposés'; }
}
async function loadInternet(){ renderInternet(await jget('/api/internet')); }
// Contrôle du navigateur (outils browser_*). Le serveur dit s'il a trouvé un
// binaire Chromium : sans lui, les outils ne sont pas annoncés au modèle, et
// l'interrupteur ne servirait qu'à promettre ce qui n'existe pas.
function renderComputer(s){
const tg=document.getElementById('computer-toggle');
if(tg) tg.checked = !!s.enabled;
// La pastille ne redit pas l'interrupteur : elle ne signale que l'anomalie
// (actif mais aucun navigateur trouvé).
if(s.enabled && !s.browser_ok) setBadge('computer-badge','warn','sans navigateur');
else setBadge('computer-badge', null);
const st=document.getElementById('computer-status');
if(!st) return;
if(!s.enabled){
st.textContent = "inactif : l'IA n'a aucun outil de navigateur" + (s.browser_ok ? '.' : " (aucun navigateur détecté dans le conteneur).");
st.style.color='';
return;
}
if(!s.browser_ok){ st.innerHTML = '⚠ aucun Chromium détecté — image bâtie avec PLAYWRIGHT=0 ? sinon définis LOKI_CHROME=&lt;chemin du binaire&gt;'; return; }
st.innerHTML = '<span style="color:var(--accent)">✓</span> navigateur prêt, outils de contrôle actifs'
+ (s.vision ? '' : ' — vision inactive : browser_screenshot indisponible');
}
async function loadComputer(){ renderComputer(await jget('/api/computer')); }
async function toggleComputer(){
const on=document.getElementById('computer-toggle').checked;
renderComputer(await jpost('/api/computer',{on}));
}
// --- Accès OpenAI (endpoint /v1 + clé API des complétions) -----------------
let OAI_KEY='', OAI_REVEAL=false;
async function copyText(txt, msg){
@@ -473,7 +499,7 @@ async function loadAll(){
// MANQUANTE lève ici une ReferenceError qui est avalée en silence. Toute
// suppression de module doit donc retirer son appel de cette ligne, et le test
// navigateur écoute `pageerror` pour ne pas s'en apercevoir trop tard.
await Promise.allSettled([loadStatus(),loadVram(),loadRam(),loadCfg(),loadPresets(),loadModelSwitchFiles(),loadConversations(),loadIdentity(),loadAgent(),loadInternet(),loadMCP(),loadApiKey(),loadPrefs(),loadLlamacpp(),loadThinkEffort(),loadTasks(),loadProjects()]);
await Promise.allSettled([loadStatus(),loadVram(),loadRam(),loadCfg(),loadPresets(),loadModelSwitchFiles(),loadConversations(),loadIdentity(),loadAgent(),loadInternet(),loadComputer(),loadMCP(),loadApiKey(),loadPrefs(),loadLlamacpp(),loadThinkEffort(),loadTasks(),loadProjects()]);
releaseHeights(); // tout est en place : on rend la main et on mesure pour la prochaine fois
}
async function act(a){ toast(a+'…'); await jpost('/api/'+a); setTimeout(loadAll,1500); }