From e96ffe319cb7e63b0adeb779717ad11f39acb4f2 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 11:11:22 +0000 Subject: [PATCH] =?UTF-8?q?API=20interne=20:=20crit=C3=A8res=20chang=C3=A9?= =?UTF-8?q?s=20par=20un=20outil,=20fichiers=20seuls=20au=20reset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /internal/v1/tool/exec rend la liste des critères après un appel de l'outil criteria qui l'a changée (add, set, clear) : critNotify la pousse au journal du Go, une conversation tenue par la façade la reçoit ici. /internal/v1/conv/dropped accepte files_only : Reset vide la discussion et ses fichiers, sans la supprimer. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv --- internal/loki/web_internal_tool.go | 27 +++++++++++++++++++++---- internal/loki/web_internal_tool_test.go | 16 +++++++++++++++ internal/loki/web_internal_turn.go | 11 +++++++++- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/internal/loki/web_internal_tool.go b/internal/loki/web_internal_tool.go index edaae25..e48a752 100644 --- a/internal/loki/web_internal_tool.go +++ b/internal/loki/web_internal_tool.go @@ -58,10 +58,29 @@ func handleInternalToolExec(w http.ResponseWriter, r *http.Request) { sendJSON(w, http.StatusOK, struct { Label string `json:"label"` toolExecResult - Image string `json:"image,omitempty"` - Shot string `json:"shot,omitempty"` - Relay *Message `json:"relay,omitempty"` - }{label, res, image, shot, relay}) + Image string `json:"image,omitempty"` + Shot string `json:"shot,omitempty"` + Relay *Message `json:"relay,omitempty"` + Criteria []Criterion `json:"criteria,omitempty"` + }{label, res, image, shot, relay, criteriaAfter(in.Name, in.Args, res)}) +} + +// criteriaAfter : la liste des critères après un appel qui l'a changée +// (critNotify la pousse au journal du Go ; une conversation tenue par la +// façade la reçoit ici). nil sinon. +func criteriaAfter(name string, args map[string]any, res toolExecResult) []Criterion { + if name != "criteria" || strings.HasPrefix(res.Result, "[erreur]") { + return nil + } + switch a, _ := args["action"].(string); a { + case "add", "set", "clear": + list := critList(convActiveID()) + if list == nil { + list = []Criterion{} + } + return list + } + return nil } // toolStepExtras : ce que runChatLoop tire d'un résultat d'outil après diff --git a/internal/loki/web_internal_tool_test.go b/internal/loki/web_internal_tool_test.go index a7efc80..2dbc9a7 100644 --- a/internal/loki/web_internal_tool_test.go +++ b/internal/loki/web_internal_tool_test.go @@ -84,3 +84,19 @@ func TestOutilInterneSuitesDuResultat(t *testing.T) { t.Fatalf("aperçu après une erreur : %q", shot) } } + +// Les critères changés par l'outil reviennent avec son résultat : une +// conversation tenue par la façade les pousse à son journal. +func TestOutilInterneCriteres(t *testing.T) { + testHome(t) + t.Setenv(internalTokenEnv, jetonTest) + got := appelOutil(t, `{"name":"criteria","args":{"action":"add","texts":["les tests passent"]},"caps":{"Agent":true,"Code":true}}`) + list, _ := got["criteria"].([]any) + if len(list) != 1 { + t.Fatalf("critères après add : %v", got) + } + got = appelOutil(t, `{"name":"criteria","args":{"action":"list"},"caps":{"Agent":true,"Code":true}}`) + if _, ok := got["criteria"]; ok { + t.Fatalf("une lecture ne pousse rien : %v", got) + } +} diff --git a/internal/loki/web_internal_turn.go b/internal/loki/web_internal_turn.go index c198f68..ebbb630 100644 --- a/internal/loki/web_internal_turn.go +++ b/internal/loki/web_internal_turn.go @@ -23,7 +23,8 @@ import ( // POST /internal/v1/turn/done {"ms":N} → 204 (Web Push si abonnés) // POST /internal/v1/conv/active {"id":…} → 204 (discussion active des outils) // POST /internal/v1/conv/dropped {"id":…} → 204 (fichiers, jobs, critères, -// mode, résultats d'outils) +// mode, résultats d'outils ; +// "files_only" : les fichiers) const internalTurnMaxBody = 8 << 20 @@ -147,6 +148,9 @@ func handleInternalConvActive(w http.ResponseWriter, r *http.Request) { func handleInternalConvDropped(w http.ResponseWriter, r *http.Request) { var in struct { ID string `json:"id"` + // FilesOnly : seulement les fichiers (Reset vide la discussion, il ne + // la supprime pas). + FilesOnly bool `json:"files_only"` } if !readInternalJSON(w, r, &in) { return @@ -155,6 +159,11 @@ func handleInternalConvDropped(w http.ResponseWriter, r *http.Request) { sendJSON(w, http.StatusBadRequest, map[string]any{"error": "id manquant"}) return } + if in.FilesOnly { + dropConvFiles(in.ID) + w.WriteHeader(http.StatusNoContent) + return + } stopConvJobs(in.ID) critDrop(in.ID) dropConvMode(in.ID)