Files
Loki/internal/nodewire/wire.go
T
Loki de6551a153 Rebaptise AJEAN en Loki (fork, lignée conservée)
- module github.com/R0m1k3/Loki, cmd/loki, internal/loki (package loki)
- LOKI_HOME, LOKI_MODEL_DIRS, LOKI_SERVICE, LOKI_DL_CONNS ; /etc/loki ;
  units loki-engine / loki-ui ; binaire et aide CLI
- updateRepo pointe sur R0m1k3/Loki (l'auto-update ne tirera plus les
  binaires AJEAN amont)

Conservé à l'identique : le domaine ajean.link (service de tunnel amont),
les littéraux de migration 0.7.x (migrate_07.go), RELEASE_NOTES.md et
LICENSE (historique et licence de l'amont).

go build/vet/test : verts.
2026-08-14 21:33:15 +00:00

119 lines
3.6 KiB
Go

// Package nodewire porte le protocole PARTAGÉ entre le serveur loki et le
// client léger « poste distant » (loki-node). Zéro dépendance hors stdlib :
// c'est ce qui permet au client de rester minuscule (il n'embarque ni l'UI, ni
// llama.cpp, ni bbolt, ni MCP — juste ce fichier + coder/websocket).
//
// Le format du fil (Msg + capacités) DOIT rester identique des deux côtés :
// centraliser ici évite toute dérive entre serveur et client.
package nodewire
import (
"errors"
"fmt"
"path/filepath"
"strings"
)
// Capacités qu'un poste peut exposer. Chacune est un pouvoir concret donné à
// l'IA sur la machine distante.
const (
CapShell = "shell" // exécuter une commande shell
CapRead = "read" // lire un fichier (confiné à la racine)
CapWrite = "write" // écrire un fichier (confiné à la racine)
CapList = "list" // lister un dossier (confiné à la racine)
)
// AllCaps est l'ordre canonique d'affichage/itération.
var AllCaps = []string{CapShell, CapRead, CapWrite, CapList}
// CapLabel donne un libellé court en français.
func CapLabel(cap string) string {
switch cap {
case CapShell:
return "shell"
case CapRead:
return "lire des fichiers"
case CapWrite:
return "écrire des fichiers"
case CapList:
return "lister des dossiers"
}
return cap
}
// Msg est l'unique enveloppe échangée sur le WebSocket, dans les deux sens.
// - client→serveur au démarrage : {type:"hello", name, os, caps:[…]}
// - serveur→client : {type:"call", id, cap, args}
// - client→serveur : {type:"result", id, result}
type Msg struct {
Type string `json:"type"`
ID string `json:"id,omitempty"`
Cap string `json:"cap,omitempty"`
Args map[string]any `json:"args,omitempty"`
Result string `json:"result,omitempty"`
Name string `json:"name,omitempty"`
OS string `json:"os,omitempty"`
Caps []string `json:"caps,omitempty"`
}
// CapAllowed indique si cap figure dans allowed.
func CapAllowed(allowed []string, cap string) bool {
for _, c := range allowed {
if c == cap {
return true
}
}
return false
}
// CapIntersect renvoie, dans l'ordre canonique, les capacités présentes dans les
// deux listes — le jeu EFFECTIF exposé à l'IA (propriétaire ∩ poste).
func CapIntersect(allowed, declared []string) []string {
var out []string
for _, c := range AllCaps {
if CapAllowed(allowed, c) && CapAllowed(declared, c) {
out = append(out, c)
}
}
return out
}
// SanitizeCaps ne garde que des capacités connues, sans doublon, dans l'ordre
// canonique. Toute entrée inconnue est ignorée.
func SanitizeCaps(caps []string) []string {
var out []string
for _, c := range AllCaps {
if CapAllowed(caps, c) {
out = append(out, c)
}
}
return out
}
// ResolvePath résout un chemin demandé À L'INTÉRIEUR de la racine autorisée et
// REFUSE tout ce qui en sortirait (traversée « .. », absolu hors racine). C'est
// le confinement des outils fichiers.
func ResolvePath(root, p string) (string, error) {
if strings.TrimSpace(root) == "" {
return "", errors.New("aucun dossier racine configuré sur ce poste")
}
if strings.TrimSpace(p) == "" {
return "", errors.New("chemin vide")
}
root = filepath.Clean(root)
var abs string
if filepath.IsAbs(p) {
abs = filepath.Clean(p)
} else {
abs = filepath.Clean(filepath.Join(root, p))
}
rel, err := filepath.Rel(root, abs)
if err != nil {
return "", fmt.Errorf("chemin invalide: %v", err)
}
if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
return "", fmt.Errorf("chemin hors du dossier autorisé (%s)", root)
}
return abs, nil
}