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
67 changes: 58 additions & 9 deletions agent-shell.el
Original file line number Diff line number Diff line change
Expand Up @@ -1702,6 +1702,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 Down Expand Up @@ -1787,7 +1800,7 @@ pretty-printed JSON inside a json fence."
(cl-defun agent-shell--on-notification (&key state acp-notification)
"Handle incoming ACP-NOTIFICATION using STATE."
(map-put! state :last-activity-time (current-time))
(cond ((and (not (agent-shell--active-requests-p state))
(cond ((and (not (agent-shell--should-render-update-p state))
(agent-shell--session-bound-notification-p acp-notification))
;; Turn-bound notification arriving with no agent request in
;; flight is a protocol violation: these notifications must
Expand Down Expand Up @@ -1907,14 +1920,17 @@ pretty-printed JSON inside a json fence."
: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 @@ -1945,6 +1961,39 @@ pretty-printed JSON inside a json fence."
: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 Down