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..ddd82bb46 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -1707,6 +1707,36 @@ _Response:_ interface ChatDeleteResponse {} ``` +### Chat update (↩️) + +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. + +_Request:_ + +* method: `chat/update` +* params: `ChatUpdateParams` defined as follows: + +```typescript +interface ChatUpdateParams { + /** + * The chat session identifier. + */ + chatId: string; + + /** + * New title for the chat. + */ + title?: string; +} +``` + +_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 20342d697..c7a957ac8 100644 --- a/src/eca/features/chat.clj +++ b/src/eca/features/chat.clj @@ -1142,6 +1142,20 @@ (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..7c0725287 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-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)))