Skip to content

Commit 96bcfe7

Browse files
ericdalloeca-agent
andcommitted
Sync trust mode on chat resume #426
Restore the per-chat trust toggle on `chat/open` and the `/resume` slash command by emitting `config/updated` with a new `selectTrust` field reflecting the resumed chat's persisted `:trust`. Previously the server kept the persisted `:trust true` (so tool calls auto-approved) while the client UI silently defaulted to shield-off, leaving them out of sync and requiring two clicks of the indicator to reach a real secured state. Closes #426 🤖 Generated with [eca](https://eca.dev) Co-Authored-By: eca-agent <git@eca.dev>
1 parent 55c176a commit 96bcfe7

6 files changed

Lines changed: 80 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Unreleased
44

55
- Add support for gpt-5.5 variants
6+
- Restore trust mode on chat resume: `chat/open` and the `/resume` slash command now emit `config/updated` with `selectTrust` reflecting the resumed chat's persisted trust toggle, so the client indicator stays in sync with the server's auto-approval behavior. #426
67

78
## 0.129.1
89

docs/protocol.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1824,9 +1824,11 @@ in the UI. The server replays the chat by emitting `chat/cleared` (messages),
18241824
the persisted messages. When the persisted chat has a stored model the server
18251825
additionally emits a `config/updated` notification to realign the client's
18261826
selected model (and available variants) with the resumed chat, so the next
1827-
prompt keeps using the chat's original provider/model. Typically used after
1828-
`chat/list` when the user selects a chat that has not been opened in the current
1829-
client session.
1827+
prompt keeps using the chat's original provider/model. The same notification
1828+
also carries `selectTrust` reflecting the resumed chat's trust toggle, so the
1829+
client indicator stays in sync with the auto-approval behavior the server will
1830+
apply. Typically used after `chat/list` when the user selects a chat that has
1831+
not been opened in the current client session.
18301832

18311833
_Request:_
18321834

@@ -2300,6 +2302,17 @@ interface ConfigUpdatedParams {
23002302
*/
23012303
selectVariant?: string | null;
23022304

2305+
/**
2306+
* The trust toggle state for the active chat. When present clients
2307+
* should forcefully update the chat trust indicator (and any
2308+
* derived UI like a shield/flame icon) to match this value.
2309+
*
2310+
* Server returns this on chat resume (`chat/open`, `/resume`) so the
2311+
* client's indicator matches the auto-approval behavior the server
2312+
* will apply for subsequent tool calls in the resumed chat.
2313+
*/
2314+
selectTrust?: boolean;
2315+
23032316
/**
23042317
* Message to show when starting a new chat.
23052318
*/

src/eca/config.clj

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,19 @@
679679
messenger
680680
db*))))
681681

682+
(defn notify-selected-trust-changed!
683+
"Server-initiated equivalent of a client-side trust toggle: aligns the
684+
client-side trust indicator with the chat's persisted `:trust` value.
685+
Emits via `notify-fields-changed-only!`, so it is a no-op when nothing
686+
changed. Used by chat resume flows (`chat/open`, `/resume`) so the icon
687+
the client shows matches the auto-approval behavior the server is about
688+
to apply (#426)."
689+
[trust db* messenger]
690+
(notify-fields-changed-only!
691+
{:chat {:select-trust (boolean trust)}}
692+
messenger
693+
db*))
694+
682695
(def ^:private config-schema-url "https://eca.dev/config.json")
683696

684697
(defn ^:private flatten-to-paths

src/eca/features/chat.clj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1479,4 +1479,5 @@
14791479
(send-chat-contents! messages chat-ctx)
14801480
(lifecycle/send-content! chat-ctx :system (assoc-some {:type :metadata} :title title))
14811481
(config/notify-selected-model-changed! (:model chat) db* messenger config)
1482+
(config/notify-selected-trust-changed! (:trust chat) db* messenger)
14821483
{:found? true :chat-id chat-id :title title}))))

src/eca/features/commands.clj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,10 @@
482482
;; Align the client's selected model with the resumed chat
483483
;; so the LLM call keeps using the chat's original model. #417
484484
(config/notify-selected-model-changed! (:model chat) db* messenger config)
485+
;; Align the client's trust indicator with the resumed
486+
;; chat's persisted :trust so the icon matches the
487+
;; auto-approval behavior the server will apply. #426
488+
(config/notify-selected-trust-changed! (:trust chat) db* messenger)
485489
{:type :chat-messages
486490
:clear-before? true
487491
:chats {chat-id {:title (:title chat)

test/eca/features/chat_test.clj

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1163,19 +1163,21 @@
11631163
(is (match? {:found? true :chat-id chat-id :title "My Opus thread"} result))
11641164
(is (match? {:config-updated [{:chat {:select-model "anthropic/claude-opus-4"
11651165
:variants []
1166-
:select-variant nil}}]}
1166+
:select-variant nil}}
1167+
{:chat {:select-trust false}}]}
11671168
(h/messages))))))
11681169

1169-
(testing "Opening a chat with no stored :model does not emit config/updated"
1170+
(testing "Opening a chat with no stored :model only emits trust config/updated"
11701171
(h/reset-components!)
11711172
(let [chat-id "no-model"]
11721173
(swap! (h/db*) assoc-in [:chats chat-id]
11731174
{:id chat-id
11741175
:messages [{:role "user" :content [{:type :text :text "hi"}]}]})
11751176
(f.chat/open-chat! {:chat-id chat-id} (h/db*) (h/messenger) (h/config))
1176-
(is (nil? (:config-updated (h/messages))))))
1177+
(is (match? {:config-updated [{:chat {:select-trust false}}]}
1178+
(h/messages)))))
11771179

1178-
(testing "Opening a chat with a stale stored :model does not emit config/updated"
1180+
(testing "Opening a chat with a stale stored :model only emits trust config/updated"
11791181
(h/reset-components!)
11801182
(let [chat-id "stale-model"]
11811183
;; Opus not in (:models db), so the UI dropdown must not jump to a ghost.
@@ -1184,6 +1186,44 @@
11841186
:model "anthropic/claude-opus-4"
11851187
:messages [{:role "user" :content [{:type :text :text "hi"}]}]})
11861188
(f.chat/open-chat! {:chat-id chat-id} (h/db*) (h/messenger) (h/config))
1187-
(is (nil? (:config-updated (h/messages)))))))
1189+
(is (match? {:config-updated [{:chat {:select-trust false}}]}
1190+
(h/messages))))))
1191+
1192+
(deftest open-chat-restores-selected-trust-test
1193+
(testing "Opening a trusted chat emits config/updated select-trust true (#426)"
1194+
(h/reset-components!)
1195+
(let [chat-id "trusted"]
1196+
(swap! (h/db*) assoc-in [:chats chat-id]
1197+
{:id chat-id
1198+
:title "YOLO thread"
1199+
:trust true
1200+
:messages [{:role "user" :content [{:type :text :text "hi"}]}]})
1201+
(f.chat/open-chat! {:chat-id chat-id} (h/db*) (h/messenger) (h/config))
1202+
(is (match? {:config-updated [{:chat {:select-trust true}}]}
1203+
(h/messages)))))
1204+
1205+
(testing "Opening a non-trusted chat emits config/updated select-trust false (#426)"
1206+
(h/reset-components!)
1207+
(let [chat-id "secured"]
1208+
;; Pre-seed last-config-notified so the diff actually picks up false.
1209+
(swap! (h/db*) assoc :last-config-notified {:chat {:select-trust true}})
1210+
(swap! (h/db*) assoc-in [:chats chat-id]
1211+
{:id chat-id
1212+
:trust false
1213+
:messages [{:role "user" :content [{:type :text :text "hi"}]}]})
1214+
(f.chat/open-chat! {:chat-id chat-id} (h/db*) (h/messenger) (h/config))
1215+
(is (match? {:config-updated [{:chat {:select-trust false}}]}
1216+
(h/messages)))))
1217+
1218+
(testing "Opening a chat with no :trust key normalizes to false (#426)"
1219+
(h/reset-components!)
1220+
(let [chat-id "legacy"]
1221+
(swap! (h/db*) assoc :last-config-notified {:chat {:select-trust true}})
1222+
(swap! (h/db*) assoc-in [:chats chat-id]
1223+
{:id chat-id
1224+
:messages [{:role "user" :content [{:type :text :text "hi"}]}]})
1225+
(f.chat/open-chat! {:chat-id chat-id} (h/db*) (h/messenger) (h/config))
1226+
(is (match? {:config-updated [{:chat {:select-trust false}}]}
1227+
(h/messages))))))
11881228

11891229

0 commit comments

Comments
 (0)