From 85da33c4a4a39cb2d4c78e4726116af3ebe3ba43 Mon Sep 17 00:00:00 2001 From: Jakub Zika Date: Wed, 8 Apr 2026 19:33:14 +0200 Subject: [PATCH 1/2] Add chat/update notification for renaming chats Persist chat titles to database and broadcast to all connected clients. Previously, renaming was client-local only (lost on restart). --- CHANGELOG.md | 2 ++ docs/protocol.md | 24 ++++++++++++++++++++++++ src/eca/features/chat.clj | 13 +++++++++++++ src/eca/handlers.clj | 4 ++++ src/eca/remote/handlers.clj | 10 ++++++++++ src/eca/remote/routes.clj | 1 + src/eca/server.clj | 3 +++ 7 files changed, 57 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ded223ec8..ed7541eae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ - Fix `/resume` broken for OpenAI chats: handle nil reasoning text during replay, preserve prompt-id after chat replacement, and clear UI before replaying messages. #400 +- Add `chat/update` notification for renaming chats. Chat titles are now persisted to the database and broadcast to all connected clients including remote web interface. + ## 0.124.2 - Fix OpenAI Responses API tool calls not executing when streaming response returns empty output, and fix spurious retries caused by stale tool-call state with Copilot encrypted IDs. #398 diff --git a/docs/protocol.md b/docs/protocol.md index 30473ec17..952c4fc6b 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -1707,6 +1707,30 @@ _Response:_ interface ChatDeleteResponse {} ``` +### Chat update (➡️) + +A client notification to update chat metadata like title. +Server will broadcast the change to all connected clients via `chat/contentReceived` with metadata content. + +_Notification:_ + +* method: `chat/update` +* params: `ChatUpdateParams` defined as follows: + +```typescript +interface ChatUpdateParams { + /** + * The chat session identifier. + */ + chatId: string; + + /** + * New title for the chat. + */ + title?: string; +} +``` + ### Chat selected agent changed (➡️) A client notification for server telling the user selected a different agent in chat. diff --git a/src/eca/features/chat.clj b/src/eca/features/chat.clj index 20342d697..99f1c18b1 100644 --- a/src/eca/features/chat.clj +++ b/src/eca/features/chat.clj @@ -1142,6 +1142,19 @@ (messenger/chat-cleared messenger {:chat-id chat-id :messages messages}) (db/update-workspaces-cache! @db* metrics))) +(defn update-chat + "Update chat metadata like title. + Broadcasts the change to all connected clients." + [{:keys [chat-id title]} db* messenger metrics] + (when (and (get-in @db* [:chats chat-id]) + title) + (swap! db* assoc-in [:chats chat-id :title] title) + (messenger/chat-content-received messenger + {:chat-id chat-id + :role "system" + :content {:type :metadata :title title}}) + (db/update-workspaces-cache! @db* metrics))) + (defn rollback-chat "Remove messages from chat in db until content-id matches. Then notify to clear chat and then the kept messages." diff --git a/src/eca/handlers.clj b/src/eca/handlers.clj index 96274279b..e127d4033 100644 --- a/src/eca/handlers.clj +++ b/src/eca/handlers.clj @@ -238,6 +238,10 @@ (metrics/task metrics :eca/chat-fork (f.chat/fork-chat params db* messenger metrics))) +(defn chat-update [{:keys [db* messenger metrics]} params] + (metrics/task metrics :eca/chat-update + (f.chat/update-chat params db* messenger metrics))) + (defn mcp-stop-server [{:keys [db* messenger metrics config]} params] (metrics/task metrics :eca/mcp-stop-server (f.tools/stop-server! (:name params) db* messenger config metrics))) diff --git a/src/eca/remote/handlers.clj b/src/eca/remote/handlers.clj index cfae2caea..3aad82e31 100644 --- a/src/eca/remote/handlers.clj +++ b/src/eca/remote/handlers.clj @@ -244,6 +244,16 @@ :variant (:variant body)}) (no-content)))))) +(defn handle-update-chat [{:keys [db*] :as components} request chat-id] + (if-not (chat-or-404 db* chat-id) + (error-response 404 "chat_not_found" (str "Chat " chat-id " does not exist")) + (let [body (parse-body request) + config (config/all @db*)] + (handlers/chat-update + (assoc components :config config) + {:chat-id chat-id :title (:title body)}) + (no-content)))) + (defn handle-set-trust [{:keys [db*]} request {:keys [sse-connections*]}] (let [body (parse-body request) trust (boolean (:trust body))] diff --git a/src/eca/remote/routes.clj b/src/eca/remote/routes.clj index 4eb986727..ba4236409 100644 --- a/src/eca/remote/routes.clj +++ b/src/eca/remote/routes.clj @@ -66,6 +66,7 @@ "model" [handlers/handle-change-model components request chat-id] "agent" [handlers/handle-change-agent components request chat-id] "variant" [handlers/handle-change-variant components request chat-id] + "update" [handlers/handle-update-chat components request chat-id] nil))) 6 (let [action (nth segments 4) tcid (nth segments 5)] diff --git a/src/eca/server.clj b/src/eca/server.clj index 34ca00cf3..fb516887e 100644 --- a/src/eca/server.clj +++ b/src/eca/server.clj @@ -115,6 +115,9 @@ (defmethod jsonrpc.server/receive-request "chat/fork" [_ components params] (eventually (handlers/chat-fork (with-config components) params))) +(defmethod jsonrpc.server/receive-notification "chat/update" [_ components params] + (async-notify (handlers/chat-update (with-config components) params))) + (defmethod jsonrpc.server/receive-notification "mcp/stopServer" [_ components params] (async-notify (handlers/mcp-stop-server (with-config components) params))) From 5adbc12e5fedff73421fab0a051d427e186a6fa8 Mon Sep 17 00:00:00 2001 From: Eric Dallo Date: Wed, 8 Apr 2026 15:05:35 -0300 Subject: [PATCH 2/2] Change chat/update from notification to request MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit State-modifying operations that persist to DB should use request semantics so the client gets acknowledgment. This aligns chat/update with chat/clear, chat/delete, chat/fork, etc. - Use receive-request + eventually instead of receive-notification - Return {} from update-chat (consistent with other chat operations) - Update protocol docs to reflect request semantics 🤖 Generated with [eca](https://eca.dev) Co-Authored-By: eca --- docs/protocol.md | 14 ++++++++++---- src/eca/features/chat.clj | 3 ++- src/eca/server.clj | 4 ++-- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/docs/protocol.md b/docs/protocol.md index 952c4fc6b..ddd82bb46 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -1707,12 +1707,12 @@ _Response:_ interface ChatDeleteResponse {} ``` -### Chat update (➡️) +### Chat update (↩️) -A client notification to update chat metadata like title. -Server will broadcast the change to all connected clients via `chat/contentReceived` with metadata content. +A client request to update chat metadata like title. +Server will persist the change, broadcast to all connected clients via `chat/contentReceived` with metadata content, and return an empty response. -_Notification:_ +_Request:_ * method: `chat/update` * params: `ChatUpdateParams` defined as follows: @@ -1731,6 +1731,12 @@ interface ChatUpdateParams { } ``` +_Response:_ + +```typescript +interface ChatUpdateResponse {} +``` + ### Chat selected agent changed (➡️) A client notification for server telling the user selected a different agent in chat. diff --git a/src/eca/features/chat.clj b/src/eca/features/chat.clj index 99f1c18b1..c7a957ac8 100644 --- a/src/eca/features/chat.clj +++ b/src/eca/features/chat.clj @@ -1153,7 +1153,8 @@ {:chat-id chat-id :role "system" :content {:type :metadata :title title}}) - (db/update-workspaces-cache! @db* metrics))) + (db/update-workspaces-cache! @db* metrics)) + {}) (defn rollback-chat "Remove messages from chat in db until content-id matches. diff --git a/src/eca/server.clj b/src/eca/server.clj index fb516887e..7c0725287 100644 --- a/src/eca/server.clj +++ b/src/eca/server.clj @@ -115,8 +115,8 @@ (defmethod jsonrpc.server/receive-request "chat/fork" [_ components params] (eventually (handlers/chat-fork (with-config components) params))) -(defmethod jsonrpc.server/receive-notification "chat/update" [_ components params] - (async-notify (handlers/chat-update (with-config components) params))) +(defmethod jsonrpc.server/receive-request "chat/update" [_ components params] + (eventually (handlers/chat-update (with-config components) params))) (defmethod jsonrpc.server/receive-notification "mcp/stopServer" [_ components params] (async-notify (handlers/mcp-stop-server (with-config components) params)))