From 60e64dd83c4f4a3b6d7afeb0a94eebbd14a77401 Mon Sep 17 00:00:00 2001 From: claude Date: Thu, 6 Aug 2026 22:59:53 +0400 Subject: [PATCH] chat gets its own core connection, so a turn stops blocking every page (V-638) ipc.Client serialises every call on one mutex, and mavweb routed 28 handlers plus /api/chat through the shared client. A turn is bounded only by phraser.timeout, 60s in deploy, so a page load behind one could wait that long. /models already had its own connection for this reason. Chat gets the third one. A failed dial logs and falls back to the shared client, which is how it behaved before. /api/ptt needs nothing here: it proxies to the voice port and never touches this client. A connection pool inside ipc.Client is the general form and stays unbuilt until a second module is measured queueing. --- cmd/mavweb/main.go | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/cmd/mavweb/main.go b/cmd/mavweb/main.go index 22578cd..a42d959 100644 --- a/cmd/mavweb/main.go +++ b/cmd/mavweb/main.go @@ -58,6 +58,12 @@ func main() { // mutex, so sharing the connection would freeze every other page for the // length of the load. See handleModels. var swapConn modelController + // turnConn — a third connection, for POST /api/chat and nothing else, for + // the same reason /models has one (V-638). A chat turn routes, phrases and + // may act, bounded only by phraser.timeout at 60s, and every other handler + // on this server queues behind it on the shared client's one mutex. Nil ⇒ + // chat shares the main connection, which is how it behaved before. + var turnConn ipc.CoreAPI if *coreSock != "" { c, err := ipc.DialWait(*coreSock, 60*time.Second) if err != nil { @@ -71,6 +77,12 @@ func main() { defer sc.Close() swapConn = sc } + if tc, err := ipc.Dial(*coreSock); err != nil { + log.Printf("chat: third core connection failed (%v) — /api/chat will share the main one and a turn will block the other pages", err) + } else { + defer tc.Close() + turnConn = tc + } } // stepUpSession stays nil unless the passkey endpoints are wired below — it @@ -208,7 +220,13 @@ func main() { // decides how every utterance is routed and how every reply is worded. mux.HandleFunc("/tools", gatedPage(handleTools)) mux.HandleFunc("/routines", gatedPage(handleRoutines)) - mux.HandleFunc("/api/chat", gatedPage(handleChatAPI)) + mux.HandleFunc("/api/chat", func(w http.ResponseWriter, r *http.Request) { + c := turnConn + if c == nil { + c = core + } + handleChatAPI(w, r, c, stepUpSession, *requireStepUp) + }) mux.HandleFunc("/api/revert", gatedPage(handleRevert)) mux.HandleFunc("/api/correct", gatedPage(handleCorrectAPI)) mux.HandleFunc("/models", func(w http.ResponseWriter, r *http.Request) {