Démarrage, captures, postes distants : correctifs portés d'AJEAN

- Au démarrage, une lecture ratée de la base (verrou bbolt transitoire pendant
  le chevauchement des process au redémarrage du conteneur) était confondue
  avec une conversation absente. Pire que l'amont chez nous : getStr rend ""
  dans les deux cas, donc convEnsureActive forgeait un NOUVEL identifiant et
  l'écrasait — le fil en cours devenait orphelin, en silence. On sonde
  désormais la base avec son erreur AVANT toute écriture, on réessaie quatre
  fois, et un échec durable est journalisé sans que rien ne soit touché.

- Une capture d'écran disparaissait sans laisser de trace : stripImageParts
  aplatissait le message en ne gardant que sa légende. Le marqueur
  imageLostMarker rend la perte VISIBLE, pour que le modèle sache reprendre
  une capture au lieu de la redécrire de mémoire.

- maxLogEvents 20000 → 200000 : un seul tour à très long raisonnement
  tronquait déjà le journal de rejeu, et l'utilisateur perdait le début de sa
  conversation à l'écran.

- Keepalive WebSocket des postes distants (ping toutes les 25 s, des DEUX
  côtés). Sans lui, un poste au repos était coupé au bout de ~60-100 s par les
  intermédiaires qui ferment les canaux inactifs, puis reconnecté après
  backoff — les déconnexions à répétition sur tous les postes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Opus 5 committed 2026-09-07 22:33:55 +02:00
1 parent 2d4f5378c0
commit c45ba615fa
6 files changed
+308 -11

No files matched your search

+33
View File
@@ -36,6 +36,13 @@ const (
maxTimeoutSec = 300
maxOutput = 8000 // caractères de stdout/stderr renvoyés
readMax = 100000 // caractères max d'une lecture de fichier
// nodePingInterval : cadence du keepalive WebSocket. 25 s tient sous les délais
// d'inactivité usuels des proxys et relais (~60-100 s). Doit rester alignée sur
// la constante homonyme côté serveur (node_server.go).
nodePingInterval = 25 * time.Second
// nodePingTimeout : au-delà, le serveur ne répond plus — on ferme, et Run
// reconnecte proprement au lieu de garder une connexion morte.
nodePingTimeout = 10 * time.Second
)
// Config persiste l'appairage et les préférences locales du poste.
@@ -245,6 +252,32 @@ func session(ctx context.Context, cfg Config, quiet bool) error {
if !quiet {
fmt.Printf("[node] connecté ✓ (canal chiffré, en attente des demandes de l'IA)\n")
}
// Keepalive : un ping WebSocket périodique empêche les intermédiaires (relais,
// Caddy, Cloudflare) de couper la connexion quand l'IA ne sollicite pas le
// poste. Sans lui, un canal inactif tombait au bout de ~60-100 s, la session se
// terminait et Run reconnectait après backoff — d'où les déconnexions à
// répétition sur tous les postes. coder/websocket sérialise Ping avec les
// écritures de données : pas de course avec sendEnc.
pingCtx, stopPing := context.WithCancel(ctx)
defer stopPing()
go func() {
t := time.NewTicker(nodePingInterval)
defer t.Stop()
for {
select {
case <-pingCtx.Done():
return
case <-t.C:
pc, cancel := context.WithTimeout(pingCtx, nodePingTimeout)
err := c.Ping(pc)
cancel()
if err != nil {
_ = c.Close(websocket.StatusGoingAway, "ping sans réponse")
return
}
}
}
}()
for {
var fr nodewire.Frame
if err := wsjson.Read(ctx, c, &fr); err != nil {