Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Unreleased

- Auto-clear completed task lists: when a new prompt is sent and all tasks are done, the list is automatically cleared.
- Bugfix: remote HTTP endpoints (`/chats`, `/session`, `/chats/:id`) now use the chat map key as the authoritative id instead of the record's `:id` field. When the two drift apart for a runtime-created chat, the chat no longer disappears from the web UI list (and on reload), and no longer loses its messages when switching tabs.

## 0.142.2

Expand Down
18 changes: 9 additions & 9 deletions src/eca/remote/handlers.clj
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,10 @@
{:name name :status (or (:status client-info) "unknown")})
(:mcp-clients db))
:chats (let [editor-open (:editor-open-chats db)]
(->> (vals (:chats db))
(remove :subagent)
(filter #(contains? editor-open (:id %)))
(mapv chat-summary)))
(->> (:chats db)
(remove (fn [[_ chat]] (:subagent chat)))
(filter (fn [[id _]] (contains? editor-open id)))
(mapv (fn [[id chat]] (chat-summary (assoc chat :id id))))))
:startedAt (when-let [ms (:started-at db)]
(.toString (Instant/ofEpochMilli ^long ms)))
:welcomeMessage (handlers/welcome-message config)
Expand Down Expand Up @@ -158,10 +158,10 @@
(defn handle-list-chats [{:keys [db*]} _request]
(let [db @db*
editor-open (:editor-open-chats db)
chats (->> (vals (:chats db))
(remove :subagent)
(filter #(contains? editor-open (:id %)))
(mapv chat-summary))]
chats (->> (:chats db)
(remove (fn [[_ chat]] (:subagent chat)))
(filter (fn [[id _]] (contains? editor-open id)))
(mapv (fn [[id chat]] (chat-summary (assoc chat :id id)))))]
(json-response chats)))

(defn handle-get-chat [{:keys [db*]} request chat-id]
Expand All @@ -176,7 +176,7 @@
{:limit (:limit params)
:before (:before params)
:after (:after params)}))
base {:id (:id chat)
base {:id chat-id
:title (:title chat)
:status (or (:status chat) :idle)
:created-at (:created-at chat)
Expand Down
25 changes: 24 additions & 1 deletion test/eca/remote/handlers_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,18 @@
by-id (into {} (map (juxt :id identity) body))]
(is (= 2 (count body)))
(is (= 2 (get-in by-id ["c1" :pendingApprovalCount])))
(is (= 0 (get-in by-id ["c2" :pendingApprovalCount]))))))
(is (= 0 (get-in by-id ["c2" :pendingApprovalCount])))))

(testing "uses the map key as the authoritative id when the :id field has drifted"
;; editor-open-chats is keyed by the map key; a runtime chat's :id field
;; may differ from its key. The list must still surface it, reported by key.
(swap! (h/db*) assoc
:chats {"key-1" {:id "stale-id" :title "Drifted" :status :idle}}
:editor-open-chats #{"key-1"})
(let [response (handlers/handle-list-chats (components) nil)
body (json/parse-string (:body response) true)]
(is (= 1 (count body)))
(is (= "key-1" (:id (first body)))))))

(deftest handle-get-chat-test
(testing "returns 404 for missing chat"
Expand All @@ -88,6 +99,18 @@
(is (= "c1" (:id body)))
(is (= "My Chat" (:title body)))))

(testing "returns the requested key as id even when the stored :id field has drifted"
;; The map key (path param) is authoritative; the client selects/restores
;; by it, so the response must echo it rather than the stale :id field.
(swap! (h/db*) assoc-in [:chats "key-1"]
{:id "stale-id" :title "Drifted" :status :idle
:messages [{:role "user" :content [{:type "text" :text "hi"}]}]})
(let [response (handlers/handle-get-chat (components) nil "key-1")
body (json/parse-string (:body response) true)]
(is (= 200 (:status response)))
(is (= "key-1" (:id body)))
(is (= 1 (count (:messages body))))))

(testing "pendingToolCalls is an empty array when no tool calls are waiting"
(swap! (h/db*) assoc-in [:chats "c1"] {:id "c1" :title "T" :status :idle})
(let [response (handlers/handle-get-chat (components) nil "c1")
Expand Down
Loading