Façade : changement de projet sans écraser la discussion, détenteur décidé par le Go

Avec LOKI_CHAT=rust, changer de projet passait par projectSwitch du Go,
qui enregistrait SA copie de la discussion — chargée au démarrage, périmée
— par-dessus celle de la façade, ouvrait une autre discussion que la façade
ignorait, et ne voyait pas un tour de la façade en cours. Désormais :

- /api/projects (switch) remet le changement à la façade
  (/internal/facade/v1/project/switch) : elle enregistre sa discussion avec
  le projet quitté, fait changer le projet actif au Go
  (/internal/v1/project/activate), ouvre la plus récente du nouveau projet
  (une neuve s'il n'en a pas) ; refusé pendant un tour (409) ;
- supprimer un projet : la façade le quitte d'abord s'il est actif, puis
  supprime ses discussions (/internal/facade/v1/project/drop).

Réglages que la façade ne sait pas honorer (PROJ_SNAPSHOT,
KEEP_TURN_IMAGES, NUDGE_IN_TOOL, SIDE_SLOT, SLOT_PERSIST, PREWARM, motifs
retry_patterns) : le Go garde la conversation, décidé une fois au démarrage
(facadeChatBlocker) ; la façade le lit (/internal/v1/chat/holder) avant de
servir quoi que ce soit, au lieu d'échouer en plein tour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
This commit is contained in:
Claude committed 2026-10-15 12:12:44 +00:00
1 parent daa2e7e4c1
commit 8fdef5455b
14 files changed
+655 -20

No files matched your search

+18 -3
View File
@@ -9,6 +9,7 @@ package loki
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
@@ -89,7 +90,8 @@ func handleProjects(w http.ResponseWriter, r *http.Request) {
err = setProjectDesc(slug, body.Desc)
case "delete":
// Une génération en cours travaille dans la mémoire du projet actif :
// supprimer sous ses pieds la laisserait écrire dans le vide.
// supprimer sous ses pieds la laisserait écrire dans le vide. La
// façade, quand elle tient la conversation, refuse elle-même (409).
if conv.isGenerating() {
sendJSON(w, 409, map[string]any{"ok": false, "error": "une génération est en cours"})
return
@@ -100,12 +102,25 @@ func handleProjects(w http.ResponseWriter, r *http.Request) {
sendJSON(w, 409, map[string]any{"ok": false, "error": "une génération est en cours"})
return
}
err = projectSwitch(slug)
if facadeChat() {
if !projectExists(slug) {
err = fmt.Errorf("projet introuvable")
} else {
err = facadeProjectSwitch(slug)
}
} else {
err = projectSwitch(slug)
}
default:
err = fmt.Errorf("action inconnue : %q", body.Action)
}
if err != nil {
sendJSON(w, 400, map[string]any{"ok": false, "error": err.Error()})
code := 400
var fe *facadeError
if errors.As(err, &fe) && fe.status == http.StatusConflict {
code = http.StatusConflict
}
sendJSON(w, code, map[string]any{"ok": false, "error": err.Error()})
return
}
sendJSON(w, 200, map[string]any{"ok": true, "projects": projectDTOs(), "active": activeProjectSlug()})