Skip to content
Open
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
98 changes: 78 additions & 20 deletions agent-shell.el
Original file line number Diff line number Diff line change
Expand Up @@ -1529,6 +1529,19 @@ COMMAND, when present, may be a shell command string or an argv vector."
((null command) nil)
(t (error "Unexpected tool-call command type: %S" (type-of command)))))

(defun agent-shell--should-render-update-p (state)
"Return non-nil if a session/update notification should render now.
True when this client has an in-flight ACP request awaiting response, or
when a session is established. The latter case covers shared sessions:
when another client also attaches to the same agent, the agent's
session/update notifications reach every attached frontend regardless
of which one originated the prompt, so this client may have no
`:active-requests' yet still receive renderable agent_message_chunks,
agent_thought_chunks, tool_calls, and tool_call_updates that should
appear in the buffer rather than being treated as stale."
(or (map-elt state :active-requests)
(map-nested-elt state '(:session :id))))

(defun agent-shell--active-requests-p (state)
"Return non-nil if STATE has in-flight requests awaiting responses."
(map-elt state :active-requests))
Expand All @@ -1539,9 +1552,12 @@ COMMAND, when present, may be a shell command string or an argv vector."
(cond ((equal (map-elt acp-notification 'method) "session/update")
(cond
((equal (map-nested-elt acp-notification '(params update sessionUpdate)) "tool_call")
;; Notification is out of context (session/prompt finished).
;; Cannot derive where to display, so show in minibuffer.
(if (not (agent-shell--active-requests-p state))
;; Render unless truly out of context (no active request and no
;; established session). When another client shares this
;; session, its prompts produce tool_call updates here that
;; are not tied to this client's :active-requests but still
;; belong in the buffer.
(if (not (agent-shell--should-render-update-p state))
(when acp-logging-enabled
(message "%s %s (stale, consider reporting to ACP agent)"
(agent-shell--make-status-kind-label
Expand Down Expand Up @@ -1593,9 +1609,10 @@ COMMAND, when present, may be a shell command string or an argv vector."
:expanded t)))
(map-put! state :last-entry-type "tool_call")))
((equal (map-nested-elt acp-notification '(params update sessionUpdate)) "agent_thought_chunk")
;; Notification is out of context (session/prompt finished).
;; Cannot derive where to display, so show in minibuffer.
(if (not (agent-shell--active-requests-p state))
;; Render unless truly out of context (no active request and no
;; established session). Prompts from another client sharing
;; this session produce thought chunks that should still appear.
(if (not (agent-shell--should-render-update-p state))
(when acp-logging-enabled
(message "%s %s (stale, consider reporting to ACP agent): %s"
agent-shell-thought-process-icon
Expand Down Expand Up @@ -1625,9 +1642,12 @@ COMMAND, when present, may be a shell command string or an argv vector."
:expanded agent-shell-thought-process-expand-by-default)
(map-put! state :last-entry-type "agent_thought_chunk")))
((equal (map-nested-elt acp-notification '(params update sessionUpdate)) "agent_message_chunk")
;; Notification is out of context (session/prompt finished).
;; Cannot derive where to display, so show in minibuffer.
(if (not (agent-shell--active-requests-p state))
;; Render unless truly out of context (no active request and no
;; established session). When another client shares this
;; session, agent_message_chunks arrive in response to its
;; prompts and should appear in the buffer rather than being
;; routed to *Messages*.
(if (not (agent-shell--should-render-update-p state))
(when acp-logging-enabled
(message "Agent message (stale, consider reporting to ACP agent): %s"
(truncate-string-to-width (map-nested-elt acp-notification '(params update content text)) 100)))
Expand Down Expand Up @@ -1656,14 +1676,17 @@ COMMAND, when present, may be a shell command string or an argv vector."
:render-body-images t)
(map-put! state :last-entry-type "agent_message_chunk")))
((equal (map-nested-elt acp-notification '(params update sessionUpdate)) "user_message_chunk")
;; Only handle user_message_chunks when there's an active session/load
;; or session/push to avoid inserting a redundant shell prompt
;; with the existing user submission.
(when (seq-find (lambda (r)
(member (map-elt r :method)
(append '("session/load")
(agent-shell-experimental--methods))))
(map-elt state :active-requests))
;; Render whenever there's somewhere to put it — local session
;; established or a history-replay request in flight. When
;; another client shares this session via a proxy, the proxy
;; is expected to exclude the originating frontend from any
;; synthesized user_message_chunk broadcast, so an arrival
;; here is from another client's typing rather than an echo
;; of this client's own session/prompt — including when that
;; own session/prompt is currently in flight. The previous
;; guard (skip while own session/prompt was active) wrongly
;; dropped those mid-turn arrivals.
(when (agent-shell--should-render-update-p state)
(let ((new-prompt-p (not (equal (map-elt state :last-entry-type)
"user_message_chunk")))
(content-text (or (map-nested-elt acp-notification '(params update content text))
Expand Down Expand Up @@ -1694,6 +1717,39 @@ COMMAND, when present, may be a shell command string or an argv vector."
:create-new new-prompt-p
:append t))
(map-put! state :last-entry-type "user_message_chunk")))
((equal (map-nested-elt acp-notification '(params update sessionUpdate)) "turn_complete")
;; Synthesized by a session-sharing proxy when a session/prompt
;; response arrives. Only the non-originating clients see this —
;; the originator gets the response directly and finalizes via
;; its :on-success callback. Without this arm, turns driven by
;; another client leave the buffer with a read-only tail and no
;; fresh prompt to type at.
(unless (seq-find (lambda (r)
(equal (map-elt r :method) "session/prompt"))
(map-elt state :active-requests))
(let* ((stop-reason (map-nested-elt acp-notification '(params update stopReason)))
(success (equal stop-reason "end_turn")))
(when (equal (map-elt state :last-entry-type) "agent_message_chunk")
(agent-shell--append-transcript
:text "\n\n"
:file-path agent-shell--transcript-file))
(map-put! state :tool-calls nil)
(unless success
(agent-shell--update-fragment
:state state
:block-id (format "%s-stop-reason"
(map-elt state :request-count))
:body (agent-shell--stop-reason-description stop-reason)
:create-new t))
(when-let ((buf (map-elt state :buffer))
((buffer-live-p buf)))
(with-current-buffer buf
(shell-maker-finish-output :config shell-maker--config
:success t)))
(agent-shell--emit-event
:event 'turn-complete
:data (list (cons :stop-reason stop-reason)))
(map-put! state :last-entry-type nil))))
((equal (map-nested-elt acp-notification '(params update sessionUpdate)) "plan")
(agent-shell--update-fragment
:state state
Expand All @@ -1703,9 +1759,11 @@ COMMAND, when present, may be a shell command string or an argv vector."
:expanded t)
(map-put! state :last-entry-type "plan"))
((equal (map-nested-elt acp-notification '(params update sessionUpdate)) "tool_call_update")
;; Notification is out of context (session/prompt finished).
;; Cannot derive where to display, so show in minibuffer.
(if (not (agent-shell--active-requests-p state))
;; Render unless truly out of context (no active request and no
;; established session). When another client shares this
;; session, its prompts produce tool_call_update events that
;; should refresh the existing card rather than being dropped.
(if (not (agent-shell--should-render-update-p state))
(when acp-logging-enabled
(message "%s %s (stale, consider reporting to ACP agent)"
(agent-shell--make-status-kind-label
Expand Down