Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Unreleased

- Auto-dismiss a pending `ask_user` question when another client (e.g. an SSE/web client in remote mode, see eca 0.139.0) answers it first. The server resolves the `ask_user` tool out from under us and sends a `toolCalled`/`toolCallRejected` for that tool-call id but no longer expects our answer (it cancels our request); we now correlate that id with `eca-chat--pending-question` and clear the stale answer-mode prompt state instead of staying stuck waiting for input.
- Add `eca-chat-delete` command to delete the active chat from the server without prompting. Works from any buffer in the project (acts on the session's last visited chat), switches the chat window to another chat first when one exists, and is bound to `C-c C-S-k` plus a `Delete` entry in the transient menu.
- Bugfix: pressing `RET` on a URL in the chat now opens it even when the URL is wrapped in markdown emphasis like `**https://...**` or `_https://..._`. The face at point is a list in that case, so the old `eq` check missed it; the trailing `**`/`_` markers are also stripped so the right URL opens. Proper `[text](url)` links still go through `markdown-follow-thing-at-point`.
- Bugfix: switching chat tabs via `tab-line-switch-to-next-tab`/`tab-line-switch-to-prev-tab` or clicking a tab now switches the chat in place instead of opening it in a new window.
Expand Down Expand Up @@ -48,7 +49,7 @@

## 0.6.0

- Add `eca-chat-save-to-file` command. #95
- Add `eca-chat-save-to-file` command. #95
- Fix chat not being closed. #89
- Fix: check existing eca sessions when opening chat. #88
- Add rewrite feature.
Expand Down
21 changes: 21 additions & 0 deletions eca-chat.el
Original file line number Diff line number Diff line change
Expand Up @@ -3128,6 +3128,9 @@ Must be called with `eca-chat--with-current-buffer' or equivalent."
(remhash id eca-chat--tool-call-prepare-content-cache)
;; Stop elapsed-time tracking for this tool call
(eca-chat--tool-call-elapsed-stop id)
;; Another client may have answered this ask_user question first,
;; resolving the tool call here; drop our now-stale prompt state.
(eca-chat--dismiss-pending-question-for-tool-call id)
;; Cleanup subagent mapping only for top-level tool calls
(when (and (not parent-tool-call-id)
(string= "subagent" (plist-get details :type)))
Expand Down Expand Up @@ -3167,6 +3170,9 @@ Must be called with `eca-chat--with-current-buffer' or equivalent."
;; Cleanup counters for this tool-call id
(remhash id eca-chat--tool-call-prepare-counters)
(remhash id eca-chat--tool-call-prepare-content-cache)
;; Another client may have cancelled this ask_user question first,
;; rejecting the tool call here; drop our now-stale prompt state.
(eca-chat--dismiss-pending-question-for-tool-call id)
;; Cleanup subagent mapping only for top-level tool calls
(when (and (not parent-tool-call-id)
(string= "subagent" (plist-get details :type)))
Expand Down Expand Up @@ -3620,6 +3626,21 @@ Used as fallback when no toolCallId is available."
session request
(list :answer nil :cancelled t)))))

(defun eca-chat--dismiss-pending-question-for-tool-call (tool-call-id)
"Dismiss the pending question when it belongs to TOOL-CALL-ID.
Used when another client answers the same `ask_user' question first, so
the server resolves the tool call out from under us. The tool output is
rendered by the caller; here we only restore the local question state
and prompt so this client does not stay stuck in answer mode."
(when (and eca-chat--pending-question
(equal tool-call-id
(plist-get eca-chat--pending-question :tool-call-id)))
(let ((allow-freeform (plist-get eca-chat--pending-question :allow-freeform)))
(setq eca-chat--pending-question nil)
(when allow-freeform
(eca-chat--set-question-prompt-prefix nil))
(eca-chat--refresh-transient-area))))

(defun eca-chat--set-question-prompt-prefix (active)
"Toggle the prompt prefix for question mode.
When ACTIVE is non-nil, show the question prefix; otherwise restore normal."
Expand Down
38 changes: 38 additions & 0 deletions test/eca-chat-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,44 @@ does not treat the first line as metadata. Returns FN's value."
(expect 'eca-chat--set-chat-loading :to-have-been-called-with
session 'stopping)))))

(describe "eca-chat--dismiss-pending-question-for-tool-call"
;; When another client answers/cancels the same `ask_user' question first,
;; the server resolves the tool call and we receive a `toolCalled' /
;; `toolCallRejected' for that id. This client must then drop its now-stale
;; pending-question state so the prompt leaves answer mode.
(it "clears the pending question when the tool-call id matches"
(with-temp-buffer
(setq-local eca-chat--pending-question
(list :session (make-eca--session) :request 1
:tool-call-id "tc-1" :allow-freeform t))
(spy-on 'eca-chat--set-question-prompt-prefix)
(spy-on 'eca-chat--refresh-transient-area)
(eca-chat--dismiss-pending-question-for-tool-call "tc-1")
(expect eca-chat--pending-question :to-be nil)
(expect 'eca-chat--set-question-prompt-prefix
:to-have-been-called-with nil)
(expect 'eca-chat--refresh-transient-area :to-have-been-called)))

(it "leaves the pending question intact when the id does not match"
(with-temp-buffer
(let ((pending (list :session (make-eca--session) :request 1
:tool-call-id "tc-1" :allow-freeform t)))
(setq-local eca-chat--pending-question pending)
(spy-on 'eca-chat--set-question-prompt-prefix)
(spy-on 'eca-chat--refresh-transient-area)
(eca-chat--dismiss-pending-question-for-tool-call "tc-2")
(expect eca-chat--pending-question :to-equal pending)
(expect 'eca-chat--set-question-prompt-prefix
:not :to-have-been-called))))

(it "does nothing when there is no pending question"
(with-temp-buffer
(setq-local eca-chat--pending-question nil)
(spy-on 'eca-chat--refresh-transient-area)
(eca-chat--dismiss-pending-question-for-tool-call "tc-1")
(expect eca-chat--pending-question :to-be nil)
(expect 'eca-chat--refresh-transient-area :not :to-have-been-called))))

(describe "eca-chat--normalize-question-option"
;; Regression: a `chat/askQuestion' option that is a plain string or a
;; plist without `:label' must not crash rendering with
Expand Down
Loading