Files
Loki/internal/loki/web_internal_toolstate.go
T
Claude a739534104 Lot 7 : outils fichiers et shell exécutés par la façade
Quand la façade tient la conversation (LOKI_CHAT=rust), read, grep, glob,
write, edit, bash et ask s'exécutent dans rust/loki-rs/src/tools au lieu de
repartir au Go par /internal/v1/tool/exec : mêmes garde-fous dans le même
ordre (commande catastrophique, « & » final en mode Code, bac à sable du
mode Code, dossier mémoire interdit, lu avant d'écrire), même diff pour
l'UI, même nettoyage de la sortie du shell. Le jeton de l'API interne est
retiré de l'environnement du shell de l'agent.

L'état partagé reste au Go, en un seul exemplaire (sous-agents et tâches
le lisent aussi) : POST /internal/v1/tool/context (dossier de travail,
dossier mémoire, cible distante), /tool/tracker (lecture notée, contrôle
avant écriture), /tool/wrote (note après écriture, diagnostics LSP).
Repli sur le Go pour une cible distante ou un contexte indisponible.

Parité : internal/loki/parity_tools_test.go passe 58 appels à execTool sur
une arborescence de test et range le journal dans tests/parity_tools ;
tests/tools.rs le retrouve à l'identique avec exec_local. Écarts assumés
consignés dans docs/superpowers/specs/2026-10-15-rust-migration-lot7-outils-design.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
2026-10-15 11:51:20 +00:00

79 lines
2.5 KiB
Go

package loki
import (
"net/http"
"strings"
)
// API interne de l'état des outils (lot 7 de la migration Rust).
//
// La façade exécute elle-même les outils fichiers et le shell (read, grep,
// glob, write, edit, bash, ask) quand elle tient la conversation. Ce qu'ils
// partagent avec le reste de Loki reste ici, en un seul exemplaire : le
// dossier de travail de la discussion, le dossier mémoire interdit au shell,
// la cible distante, le suivi « lu avant d'écrire » (lu aussi par les
// sous-agents et les tâches, qui tournent ici) et les diagnostics LSP.
//
// POST /internal/v1/tool/context {}
// → {"cwd":"…","memory_dir":"…","target":"slug|"}
// POST /internal/v1/tool/tracker {"op":"read","path":"/abs"} → {}
// POST /internal/v1/tool/tracker {"op":"check","path":"/abs","for_write":true}
// → {"refusal":"[refusé] …"|""}
// POST /internal/v1/tool/wrote {"path":"/abs","caps":{…}}
// → {"lsp":"\n\n[diagnostics LSP] …"|""}
//
// wrote : après une écriture réussie (write/edit) — note du tracker, puis le
// bloc des diagnostics LSP que la boucle ajoute au résultat de l'outil.
func handleInternalToolContext(w http.ResponseWriter, r *http.Request) {
var in struct{}
if !readInternalJSON(w, r, &in) {
return
}
sendJSON(w, http.StatusOK, map[string]any{
"cwd": agentCwd(),
"memory_dir": memoryDir(),
"target": agentTargetSlug(),
})
}
func handleInternalToolTracker(w http.ResponseWriter, r *http.Request) {
var in struct {
Op string `json:"op"`
Path string `json:"path"`
ForWrite bool `json:"for_write"`
}
if !readInternalJSON(w, r, &in) {
return
}
if strings.TrimSpace(in.Path) == "" {
sendJSON(w, http.StatusBadRequest, map[string]any{"error": "chemin manquant"})
return
}
switch in.Op {
case "read":
trackerNoteRead(in.Path)
sendJSON(w, http.StatusOK, map[string]any{})
case "check":
sendJSON(w, http.StatusOK, map[string]any{"refusal": trackerCheck(in.Path, in.ForWrite)})
default:
sendJSON(w, http.StatusBadRequest, map[string]any{"error": "op inconnue"})
}
}
func handleInternalToolWrote(w http.ResponseWriter, r *http.Request) {
var in struct {
Path string `json:"path"`
Caps Caps `json:"caps"`
}
if !readInternalJSON(w, r, &in) {
return
}
if strings.TrimSpace(in.Path) == "" {
sendJSON(w, http.StatusBadRequest, map[string]any{"error": "chemin manquant"})
return
}
trackerNoteWrite(in.Path)
sendJSON(w, http.StatusOK, map[string]any{"lsp": lspDiagBlock(in.Path, in.Caps)})
}