@@ -245,40 +245,56 @@ Generates constructor TYPE-NAME--make plus:
245245
246246;;; Executor registry
247247
248- (defvar agent-shell-queue--executor-registry (make-hash-table :test #'equal)
249- "Registry mapping executor name strings to executor functions.
250- Only registered executors survive serialization. Register entries with
248+ (cl-defstruct (agent-shell-queue-executor
249+ (:constructor agent-shell-queue-executor--make)
250+ (:copier nil))
251+ "Registry entry pairing a serializable NAME with an EXECUTOR function and
252+ an optional CAPTURE function.
253+ EXECUTOR: (item args) — called by `agent-shell-queue-send-item' to dispatch.
254+ CAPTURE: () — called during item creation to produce the args value;
255+ nil means fall back to the standard text-capture buffer."
256+ name executor capture)
257+
258+ (defvar agent-shell-queue--executors nil
259+ "List of `agent-shell-queue-executor' entries.
260+ Only executors present here survive serialization. Register entries with
251261`agent-shell-queue-register-executor'.")
252262
253- (defun agent-shell-queue-register-executor (name fn )
254- "Register executor function FN under NAME.
255- NAME may be a string or symbol; it is stored as a string via `symbol-name'
256- when a symbol is passed . The name is what gets written into serialized
263+ (defun agent-shell-queue-register-executor (name executor &optional capture )
264+ "Register EXECUTOR (and optional CAPTURE) under NAME.
265+ NAME may be a string or symbol; it is coerced to a string. Re-registering
266+ an existing name replaces the entry . The name is written into serialized
257267queue state, so it must be stable across Emacs restarts.
258- Returns FN."
259- (puthash (if (symbolp name) (symbol-name name) name)
260- fn agent-shell-queue--executor-registry)
261- fn)
268+ Returns EXECUTOR."
269+ (let ((name-str (if (symbolp name) (symbol-name name) name)))
270+ (setq agent-shell-queue--executors
271+ (cons (agent-shell-queue-executor--make
272+ :name name-str :executor executor :capture capture)
273+ (seq-remove (lambda (e) (equal name-str (agent-shell-queue-executor-name e)))
274+ agent-shell-queue--executors)))
275+ executor))
276+
277+ (defun agent-shell-queue--find-executor (name)
278+ "Return the `agent-shell-queue-executor' entry for NAME, or nil."
279+ (seq-find (lambda (e) (equal name (agent-shell-queue-executor-name e)))
280+ agent-shell-queue--executors))
262281
263282(defun agent-shell-queue--executor-name (fn)
264- "Return the registry name for FN, or nil if FN is not registered.
265- Performs a reverse lookup (value → key) over the registry, so the
266- registered name need not match the function's symbol name. Returns nil
267- for nil, or for any function not found in the registry."
268- (when fn
269- (map-some (lambda (name registered-fn)
270- (when (eq fn registered-fn) name))
271- agent-shell-queue--executor-registry)))
283+ "Return the registry name for FN, or nil if not registered."
284+ (when-let* ((e (seq-find (lambda (e) (eq fn (agent-shell-queue-executor-executor e)))
285+ agent-shell-queue--executors)))
286+ (agent-shell-queue-executor-name e)))
272287
273288(defun agent-shell-queue--executor-from-plist (name)
274289 "Deserialize executor NAME via the registry.
275290Returns nil (kind-dispatch) when NAME is nil or not found; emits a
276291warning for non-nil names that have no registry entry."
277292 (when name
278- (or (gethash name agent-shell-queue--executor-registry)
279- (progn
280- (message "agent-shell-queue: unknown executor %S — item will use kind dispatch" name)
281- nil))))
293+ (if-let* ((e (agent-shell-queue--find-executor name)))
294+ (agent-shell-queue-executor-executor e)
295+ (progn
296+ (message "agent-shell-queue: unknown executor %S — item will use kind dispatch" name)
297+ nil))))
282298
283299;;; Data model
284300
@@ -539,11 +555,15 @@ the session queue is paused until the mode changes.")
539555 (apply #'string
540556 (seq-map (lambda (_it) (aref chars (random 36))) (make-list 4 nil))))))
541557
558+ (defun agent-shell-queue--clean-args (args)
559+ "Remove trailing whitespace from every line of ARGS."
560+ (string-join (seq-map #'string-trim-right (split-string args "\n")) "\n"))
561+
542562(defun agent-shell-queue--make-item (prompt &optional background kind)
543563 "Return a new active queue item for PROMPT."
544564 (agent-shell-queue-item--make
545565 :id (agent-shell-queue--gen-id)
546- :args prompt
566+ :args (agent-shell-queue--clean-args prompt)
547567 :status 'active
548568 :kind (or kind 'prompt)
549569 :background background
@@ -1087,9 +1107,8 @@ Returns t to proceed, nil to skip. When user answers \\='a\\=', sets
10871107
10881108(defun agent-shell-queue-edit (id new-prompt)
10891109 "Replace the args of item ID with NEW-PROMPT. Save."
1090- ;; does it make sense to make a "agent-shell-queue--with-save" macro and then inline all of this?
10911110 (when-let* ((pair (agent-shell-queue--item-by-id id)))
1092- (setf (agent-shell-queue-item-args (cdr pair)) new-prompt)
1111+ (setf (agent-shell-queue-item-args (cdr pair)) (agent-shell-queue--clean-args new-prompt) )
10931112 (agent-shell-queue--save)))
10941113
10951114(defun agent-shell-queue-set-background-task (id flag)
@@ -1233,7 +1252,8 @@ insertion so response capture can find the reply."
12331252
12341253(agent-shell-queue-register-executor
12351254 (symbol-name 'agent-shell-queue--default-executor)
1236- #'agent-shell-queue--default-executor)
1255+ #'agent-shell-queue--default-executor
1256+ nil)
12371257
12381258(defun agent-shell-queue-send-item (id)
12391259 "Send the item with ID to its target buffer, marking it as running.
@@ -1325,36 +1345,50 @@ all collapsed blocks are folded: only the prose between them."
13251345 (> (prop-match-beginning end-marker) start-pos))
13261346 (prop-match-beginning end-marker)
13271347 (point-max)))
1328- ;; The echoed prompt has field=input; model response
1329- ;; starts where field transitions away from input.
1348+ ;; If start-pos is still inside the echoed field=input
1349+ ;; region, skip forward to where field changes (the
1350+ ;; model response). If shell-maker already advanced
1351+ ;; past field=input before the position was recorded
1352+ ;; (the common case), use start-pos directly — a
1353+ ;; next-single-property-change call here would return
1354+ ;; the field change at the very end of the response,
1355+ ;; making response-start equal to end-pos and
1356+ ;; collecting nothing.
13301357 (response-start
1331- (or (next-single-property-change start-pos 'field nil end-pos)
1332- start-pos))
1333- ;; Walk forward collecting plain-text segments (no
1334- ;; agent-shell-ui-state), skipping collapsed blocks.
1358+ (if (eq (get-text-property start-pos 'field) 'input)
1359+ (or (next-single-property-change start-pos 'field nil end-pos)
1360+ start-pos)
1361+ start-pos))
1362+ ;; Walk forward, keeping only the last visible segment.
1363+ ;; Thinking blocks, tool calls, and intermediate agent
1364+ ;; messages are overwritten each iteration; only the
1365+ ;; final non-empty visible block is retained.
13351366 (pos response-start)
1336- (segments nil))
1367+ (last-seg nil))
13371368 (while (< pos end-pos)
13381369 (let* ((state (get-text-property pos 'agent-shell-ui-state))
13391370 (block-end (or (next-single-property-change
13401371 pos 'agent-shell-ui-state nil end-pos)
13411372 end-pos)))
13421373 (if (and state (text-property-any pos block-end 'invisible t))
1343- ;; Block has hidden body (collapsed tool call, thinking, etc.) — skip.
1374+ ;; Hidden body (collapsed tool call, thinking, etc.) — skip.
13441375 (setq pos block-end)
1345- ;; Visible content (plain text, agent message, expanded block) — collect .
1376+ ;; Visible content — overwrite; we want only the last one .
13461377 (let ((seg (string-trim
13471378 (buffer-substring-no-properties pos block-end))))
13481379 (when (not (string-empty-p seg))
1349- (push seg segments ))
1380+ (setq last- seg seg ))
13501381 (setq pos block-end)))))
1351- (when segments
1352- (string-join (nreverse segments) "\n\n")))))))
1353- (when (and text (not (string-empty-p text)))
1354- (setf (agent-shell-queue-item-response (cdr pair))
1355- (if (> (length text) 8192)
1356- (concat (substring text 0 8192) "\n\n…[truncated]")
1357- text)))))))
1382+ last-seg)))))
1383+ (if (and text (not (string-empty-p text)))
1384+ (let ((stored (if (> (length text) 8192)
1385+ (concat (substring text 0 8192) "\n\n…[truncated]")
1386+ text)))
1387+ (setf (agent-shell-queue-item-response (cdr pair)) stored)
1388+ (message "agent-shell-queue: captured response for %s (%d chars%s)"
1389+ id (length text)
1390+ (if (> (length text) 8192) ", truncated" "")))
1391+ (message "agent-shell-queue: no response captured for %s" id))))))
13581392
13591393(defun agent-shell-queue--mark-running-done (buf-name)
13601394 "Mark any running items for BUF-NAME as done, recording completion time.
0 commit comments