Skip to content

Commit 5e46a96

Browse files
ericdalloeca-agent
andcommitted
Improve stop prompt reliability and fix multiple race conditions
- Fix duplicate "Prompt stopped" message when stopping with a running subagent by adding silent mode to prompt-stop - Fix delayed newline after stop by suppressing belated statusChanged notification from the finally block via prompt-finished? flag - Make finish-chat-prompt! idempotent — hooks and progress fire exactly once per prompt regardless of how many callers invoke it - Add prompt-finished? to cancelled? callback and assert-chat-not-stopped! to block LLM retries and bail out stream callbacks after finish - Fix stop during auto-compacting leaving the flag stuck forever by clearing compacting flags in prompt-stop - Suppress error message text sent to client when stopping (cosmetic) - Reduce stream watchdog poll interval from 2000ms to 500ms for faster stop responsiveness 🤖 Generated with [eca](https://eca.dev) Co-Authored-By: eca <git@eca.dev>
1 parent f37b655 commit 5e46a96

File tree

2 files changed

+35
-28
lines changed

2 files changed

+35
-28
lines changed

src/eca/features/chat.clj

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,7 @@
525525
:cancelled? (fn []
526526
(let [chat (get-in @db* [:chats chat-id])]
527527
(or (identical? :stopping (:status chat))
528+
(:prompt-finished? chat)
528529
(not= prompt-id (:prompt-id chat)))))
529530
:on-retry (fn [{:keys [attempt max-retries delay-ms classified]}]
530531
(let [{error-type :error/type error-label :error/label} classified
@@ -759,7 +760,8 @@
759760
:auto-continue
760761
(assoc chat-ctx :auto-continued? true))))))
761762
(do
762-
(lifecycle/send-content! chat-ctx :system {:type :text :text (str "\n\n" (or message (str "Error: " (or (ex-message exception) (.getName (class exception))))))})
763+
(when-not stopping?
764+
(lifecycle/send-content! chat-ctx :system {:type :text :text (str "\n\n" (or message (str "Error: " (or (ex-message exception) (.getName (class exception))))))}))
763765
(lifecycle/finish-chat-prompt! :idle (dissoc chat-ctx :on-finished-side-effect))))))))})
764766
(catch Exception e
765767
(when-not (:silent? (ex-data e))
@@ -1046,6 +1048,8 @@
10461048
(tc/transition-tool-call! db* chat-ctx tool-call-id :stop-requested
10471049
{:reason {:code :user-prompt-stop
10481050
:text "Tool call rejected because of user prompt stop"}}))
1051+
;; Clear compacting flags so finish-chat-prompt! isn't blocked
1052+
(swap! db* update-in [:chats chat-id] dissoc :auto-compacting? :compacting?)
10491053
(lifecycle/finish-chat-prompt! :stopping (dissoc chat-ctx :on-finished-side-effect))))))
10501054

10511055
(defn delete-chat

src/eca/features/chat/lifecycle.clj

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -68,32 +68,33 @@
6868
content))
6969

7070
(defn finish-chat-prompt! [status {:keys [message chat-id db* messenger metrics config on-finished-side-effect prompt-id] :as chat-ctx}]
71-
(when-not (and prompt-id (not= prompt-id (get-in @db* [:chats chat-id :prompt-id])))
72-
(when-not (get-in @db* [:chats chat-id :auto-compacting?])
73-
(swap! db* assoc-in [:chats chat-id :status] status)
74-
(messenger/chat-status-changed messenger {:chat-id chat-id :status status})
75-
(let [db @db*
76-
subagent? (some? (get-in db [:chats chat-id :subagent]))
77-
hook-type (if subagent? :subagentPostRequest :postRequest)
78-
hook-data (cond-> (merge (f.hooks/chat-hook-data db chat-id (:agent chat-ctx))
79-
{:prompt message})
80-
subagent? (assoc :parent-chat-id (get-in db [:chats chat-id :parent-chat-id])))]
81-
(f.hooks/trigger-if-matches! hook-type
82-
hook-data
83-
{:on-before-action (partial notify-before-hook-action! chat-ctx)
84-
:on-after-action (partial notify-after-hook-action! chat-ctx)}
85-
db
86-
config))
87-
(send-content! chat-ctx :system
88-
{:type :progress
89-
:state :finished})
90-
(when-not (get-in @db* [:chats chat-id :created-at])
91-
(swap! db* assoc-in [:chats chat-id :created-at] (System/currentTimeMillis)))
92-
(swap! db* assoc-in [:chats chat-id :prompt-finished?] true))
93-
(when (and on-finished-side-effect
94-
(not (identical? :stopping (get-in @db* [:chats chat-id :status]))))
95-
(on-finished-side-effect))
96-
(db/update-workspaces-cache! @db* metrics)))
71+
(when-not (get-in @db* [:chats chat-id :prompt-finished?])
72+
(when-not (and prompt-id (not= prompt-id (get-in @db* [:chats chat-id :prompt-id])))
73+
(when-not (get-in @db* [:chats chat-id :auto-compacting?])
74+
(swap! db* assoc-in [:chats chat-id :prompt-finished?] true)
75+
(swap! db* assoc-in [:chats chat-id :status] status)
76+
(messenger/chat-status-changed messenger {:chat-id chat-id :status status})
77+
(let [db @db*
78+
subagent? (some? (get-in db [:chats chat-id :subagent]))
79+
hook-type (if subagent? :subagentPostRequest :postRequest)
80+
hook-data (cond-> (merge (f.hooks/chat-hook-data db chat-id (:agent chat-ctx))
81+
{:prompt message})
82+
subagent? (assoc :parent-chat-id (get-in db [:chats chat-id :parent-chat-id])))]
83+
(f.hooks/trigger-if-matches! hook-type
84+
hook-data
85+
{:on-before-action (partial notify-before-hook-action! chat-ctx)
86+
:on-after-action (partial notify-after-hook-action! chat-ctx)}
87+
db
88+
config))
89+
(send-content! chat-ctx :system
90+
{:type :progress
91+
:state :finished})
92+
(when-not (get-in @db* [:chats chat-id :created-at])
93+
(swap! db* assoc-in [:chats chat-id :created-at] (System/currentTimeMillis))))
94+
(when (and on-finished-side-effect
95+
(not (identical? :stopping (get-in @db* [:chats chat-id :status]))))
96+
(on-finished-side-effect))
97+
(db/update-workspaces-cache! @db* metrics))))
9798

9899
(defn maybe-renew-auth-token [chat-ctx]
99100
(f.login/maybe-renew-auth-token!
@@ -111,7 +112,9 @@
111112
(defn assert-chat-not-stopped! [{:keys [chat-id db* prompt-id] :as chat-ctx}]
112113
(let [chat (get-in @db* [:chats chat-id])
113114
superseded? (and prompt-id (not= prompt-id (:prompt-id chat)))
114-
stopped? (or (identical? :stopping (:status chat)) superseded?)]
115+
stopped? (or (identical? :stopping (:status chat))
116+
(:prompt-finished? chat)
117+
superseded?)]
115118
(when stopped?
116119
(finish-chat-prompt! :idle (dissoc chat-ctx :on-finished-side-effect))
117120
(logger/info logger-tag "Chat prompt stopped:" chat-id (when superseded? "(superseded)"))

0 commit comments

Comments
 (0)