Skip to content

Commit 24aa9a1

Browse files
committed
Merge branch 'queue-via-temp-gfm-compose-buffer' into dev
2 parents e45d0f5 + 99c3395 commit 24aa9a1

5 files changed

Lines changed: 473 additions & 18 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,15 @@ jobs:
150150
repository: xenodium/shell-maker
151151
path: deps/shell-maker
152152

153+
# Pin to the Package-Requires floor (v2.5) so CI catches code that
154+
# silently depends on a newer markdown-mode. bin/test auto-discovers
155+
# whatever's installed locally and may pick up something newer.
156+
- uses: actions/checkout@v4
157+
with:
158+
repository: jrblevin/markdown-mode
159+
ref: v2.5
160+
path: deps/markdown-mode
161+
153162
- uses: purcell/setup-emacs@master
154163
with:
155164
version: 29.4
@@ -164,7 +173,7 @@ jobs:
164173
case "$f" in x.*|y.*|z.*) ;; *) compile_files+=("$f") ;; esac
165174
done
166175
emacs -Q --batch \
167-
-L . -L deps/acp.el -L deps/shell-maker \
176+
-L . -L deps/acp.el -L deps/shell-maker -L deps/markdown-mode \
168177
-f batch-byte-compile \
169178
"${compile_files[@]}"
170179
@@ -175,6 +184,6 @@ jobs:
175184
test_args+=(-l "$f")
176185
done
177186
emacs -Q --batch \
178-
-L . -L deps/acp.el -L deps/shell-maker -L tests \
187+
-L . -L deps/acp.el -L deps/shell-maker -L deps/markdown-mode -L tests \
179188
"${test_args[@]}" \
180189
-f ert-run-tests-batch-and-exit

‎README.org‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ A soft fork of [[https://github.com/xenodium/agent-shell][agent-shell]] with ext
2525
- Tunable markdown-overlay debounce via =agent-shell-markdown-overlay-debounce-delay= (default 0.15s) for slow terminals or streaming-debug sessions
2626
- Bug fix for upstream =shell-maker-define-major-mode= mode-map quoting — without it, every =agent-shell-mode= invocation emits =void-function keymap= because the bare keymap value gets spliced into a backquote that re-evaluates =(keymap ...)= as a function call (worth upstreaming separately)
2727
- Live-validate workflow doc (=.agents/commands/live-validate.md=) describing the batch-mode rendering verification used for rendering-pipeline changes
28+
- =gfm-mode= compose buffer for the interactive =agent-shell-queue-request=, replacing the read-string minibuffer prompt (non-interactive callers still pass =PROMPT= directly)
2829

2930
-----
3031

@@ -782,6 +783,9 @@ always go to Evil modes if you need to with ~C-z~).
782783
| | agent-shell-anthropic-start-claude-code | Start an interactive Claude Agent shell. |
783784
| | agent-shell-auggie-start-agent | Start an interactive Auggie agent shell. |
784785
| | agent-shell-clear-buffer | Clear the current shell buffer. |
786+
| | agent-shell-queue-compose-cancel | Cancel the compose buffer (asks confirmation when modified). |
787+
| | agent-shell-queue-compose-mode | Minor mode for the agent-shell queue-request compose buffer. |
788+
| | agent-shell-queue-compose-submit | Submit (or queue) the contents of the compose buffer. |
785789
| | agent-shell-completion-mode | Toggle agent shell completion with @ or / prefix. |
786790
| | agent-shell-cursor-start-agent | Start an interactive Cursor agent shell. |
787791
| C-<tab> | agent-shell-cycle-session-mode | Cycle through available session modes for the current `agent-shell' session. |
@@ -811,7 +815,7 @@ always go to Evil modes if you need to with ~C-z~).
811815
| p or <backtab> | agent-shell-previous-item | Go to previous item. |
812816
| | agent-shell-previous-permission-button | Jump to the previous button. |
813817
| | agent-shell-prompt-compose | Compose an `agent-shell' prompt in a dedicated buffer. |
814-
| | agent-shell-queue-request | Queue or immediately send a request depending on shell busy state. |
818+
| | agent-shell-queue-request | Compose (interactive) or send PROMPT (non-interactive) — busy-aware. |
815819
| | agent-shell-qwen-start | Start an interactive Qwen Code CLI agent shell. |
816820
| | agent-shell-remove-pending-request | Remove all pending requests or a specific request by REMOVE-INDEX. |
817821
| C-x x r | agent-shell-rename-buffer | Rename current shell buffer. |

‎agent-shell.el‎

Lines changed: 149 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
;; Author: Alvaro Ramirez https://xenodium.com
66
;; URL: https://github.com/xenodium/agent-shell
77
;; Version: 0.50.1
8-
;; Package-Requires: ((emacs "29.1") (shell-maker "0.90.1") (acp "0.11.1"))
8+
;; Package-Requires: ((emacs "29.1") (shell-maker "0.90.1") (acp "0.11.1") (markdown-mode "2.5"))
99

1010
(defconst agent-shell--version "0.50.1")
1111

@@ -46,6 +46,7 @@
4646
(require 'diff)
4747
(require 'json)
4848
(require 'map)
49+
(require 'markdown-mode)
4950
(unless (require 'markdown-overlays nil 'noerror)
5051
(error "Please update 'shell-maker' to v0.90.1 or newer"))
5152
(require 'agent-shell-invariants)
@@ -7282,22 +7283,28 @@ Remove: M-x agent-shell-remove-pending-request
72827283
(append pending (list prompt)))
72837284
(message "Request queued (%d pending)" (length (map-elt agent-shell--state :pending-requests)))))
72847285

7285-
(defun agent-shell-queue-request (prompt)
7286+
(defun agent-shell-queue-request (&optional prompt)
72867287
"Queue or immediately send a request depending on shell busy state.
72877288

7288-
Read PROMPT from the minibuffer. If the shell is busy, add it to the pending
7289-
requests queue. Otherwise, submit it immediately. Queued requests will be
7290-
automatically sent when the current request completes."
7291-
(interactive
7292-
(progn
7293-
(unless (derived-mode-p 'agent-shell-mode)
7294-
(error "Not in a shell"))
7295-
(list (read-string (or (map-nested-elt (agent-shell--state) '(:agent-config :shell-prompt))
7296-
"Enqueue request: ")))))
7297-
(agent-shell--idle-notification-cancel)
7298-
(if (shell-maker-busy)
7299-
(agent-shell--enqueue-request :prompt prompt)
7300-
(agent-shell--insert-to-shell-buffer :text prompt :submit t :no-focus t)))
7289+
Interactively, pop a `gfm-mode' compose buffer; submit on
7290+
\\<agent-shell-queue-compose-mode-map>\\[agent-shell-queue-compose-submit].
7291+
If the shell is busy when submitted, add to the pending requests
7292+
queue; otherwise submit immediately. Queued requests will be
7293+
automatically sent when the current request completes.
7294+
7295+
When called non-interactively with PROMPT, submit or queue
7296+
PROMPT directly, bypassing the compose buffer."
7297+
(declare (modes agent-shell-mode))
7298+
(interactive)
7299+
(unless (derived-mode-p 'agent-shell-mode)
7300+
(user-error "Not in a shell"))
7301+
(cond
7302+
((not prompt)
7303+
(agent-shell-queue-compose-pop (current-buffer)))
7304+
((string-empty-p (string-trim prompt))
7305+
(user-error "PROMPT is empty"))
7306+
(t
7307+
(agent-shell--queue-or-submit prompt (current-buffer)))))
73017308

73027309
(defun agent-shell-resume-pending-requests ()
73037310
"Resume processing pending requests in the queue."
@@ -7348,6 +7355,133 @@ or select a specific request to remove."
73487355
(map-put! agent-shell--state :pending-requests nil)
73497356
(message "Removed all pending requests"))))
73507357

7358+
;;; Queue compose
7359+
7360+
(defvar-local agent-shell-queue-compose--shell-buffer nil
7361+
"Originating agent-shell buffer this compose buffer submits to.")
7362+
7363+
(defvar-local agent-shell--queue-compose-buffer nil
7364+
"Compose buffer associated with this shell buffer, if any.")
7365+
7366+
(defvar agent-shell-queue-compose-mode-map
7367+
(let ((map (make-sparse-keymap)))
7368+
(define-key map (kbd "C-c C-c") #'agent-shell-queue-compose-submit)
7369+
(define-key map (kbd "C-c C-k") #'agent-shell-queue-compose-cancel)
7370+
map)
7371+
"Keymap for `agent-shell-queue-compose-mode'.")
7372+
7373+
(define-minor-mode agent-shell-queue-compose-mode
7374+
"Minor mode for the agent-shell queue-request compose buffer.
7375+
7376+
Layered on top of `gfm-mode' so its keymap takes precedence over the
7377+
major-mode bindings.
7378+
7379+
\\{agent-shell-queue-compose-mode-map}"
7380+
:lighter " ASQCompose"
7381+
:keymap agent-shell-queue-compose-mode-map)
7382+
7383+
(defun agent-shell--queue-or-submit (prompt shell-buffer)
7384+
"Queue PROMPT or submit it to SHELL-BUFFER depending on busy state.
7385+
7386+
The submit branch's `agent-shell--insert-to-shell-buffer' cancels
7387+
any active idle notification itself; the busy branch can't have
7388+
one active because the notification only fires when the shell is
7389+
idle."
7390+
(unless (buffer-live-p shell-buffer)
7391+
(user-error "Shell buffer is not live"))
7392+
(with-current-buffer shell-buffer
7393+
(if (shell-maker-busy)
7394+
(agent-shell--enqueue-request :prompt prompt)
7395+
(agent-shell--insert-to-shell-buffer
7396+
:shell-buffer shell-buffer :text prompt :submit t :no-focus t))))
7397+
7398+
(defun agent-shell-queue-compose-pop (shell-buffer)
7399+
"Pop a `gfm-mode' compose buffer bound to SHELL-BUFFER.
7400+
7401+
Reuses the shell's existing compose buffer when alive so an
7402+
in-progress draft survives a re-invocation."
7403+
(unless (buffer-live-p shell-buffer)
7404+
(user-error "Shell buffer is not live"))
7405+
(let* ((existing (buffer-local-value 'agent-shell--queue-compose-buffer
7406+
shell-buffer))
7407+
;; Strip leading/trailing asterisks from the shell name so the
7408+
;; compose buffer doesn't render as `*…: *shell**`.
7409+
(shell-stem (string-trim (buffer-name shell-buffer) "\\*+" "\\*+"))
7410+
(buffer (if (buffer-live-p existing)
7411+
(with-current-buffer existing
7412+
;; Reset modified flag on reuse so the modeline
7413+
;; doesn't carry the `**' indicator from a kept
7414+
;; draft into the next session.
7415+
(set-buffer-modified-p nil)
7416+
existing)
7417+
(let ((new (generate-new-buffer
7418+
(format "*agent-shell-queue-compose: %s*" shell-stem))))
7419+
(with-current-buffer new
7420+
(gfm-mode)
7421+
(agent-shell-queue-compose-mode 1)
7422+
(setq agent-shell-queue-compose--shell-buffer shell-buffer)
7423+
(add-hook 'kill-buffer-hook
7424+
#'agent-shell-queue-compose--clear-shell-ref
7425+
nil t)
7426+
(setq header-line-format
7427+
(substitute-command-keys
7428+
"Compose request — \\[agent-shell-queue-compose-submit] queue/submit · \\[agent-shell-queue-compose-cancel] cancel"))
7429+
(set-buffer-modified-p nil))
7430+
(with-current-buffer shell-buffer
7431+
(setq agent-shell--queue-compose-buffer new))
7432+
new))))
7433+
(pop-to-buffer buffer)
7434+
buffer))
7435+
7436+
(defun agent-shell-queue-compose--clear-shell-ref ()
7437+
"Clear the originating shell's compose-buffer pointer.
7438+
Run from `kill-buffer-hook' on the compose buffer so a killed
7439+
buffer doesn't leave a stale reference behind."
7440+
(let ((shell-buffer agent-shell-queue-compose--shell-buffer)
7441+
(this-buffer (current-buffer)))
7442+
(when (buffer-live-p shell-buffer)
7443+
(with-current-buffer shell-buffer
7444+
(when (eq agent-shell--queue-compose-buffer this-buffer)
7445+
(setq agent-shell--queue-compose-buffer nil))))))
7446+
7447+
(defun agent-shell-queue-compose--quit-or-kill ()
7448+
"Kill the compose buffer, also quitting its window when displayed.
7449+
`quit-window t' kills the buffer and restores the previous window
7450+
state; if the buffer isn't currently displayed (e.g. invoked via
7451+
\\[execute-extended-command] after switching away), `quit-window'
7452+
would pick an arbitrary window — fall back to plain `kill-buffer'."
7453+
(if (get-buffer-window (current-buffer) t)
7454+
(quit-window t)
7455+
(kill-buffer (current-buffer))))
7456+
7457+
(defun agent-shell-queue-compose-submit ()
7458+
"Submit (or queue) the contents of the compose buffer."
7459+
(interactive)
7460+
(unless agent-shell-queue-compose-mode
7461+
(user-error "Not in an agent-shell compose buffer"))
7462+
(let ((shell-buffer agent-shell-queue-compose--shell-buffer)
7463+
(prompt (buffer-string)))
7464+
(when (string-empty-p (string-trim prompt))
7465+
(user-error "Compose buffer is empty"))
7466+
(unless (buffer-live-p shell-buffer)
7467+
(user-error "Originating shell buffer is no longer live"))
7468+
(agent-shell--queue-or-submit prompt shell-buffer)
7469+
(agent-shell-queue-compose--quit-or-kill)))
7470+
7471+
(defun agent-shell-queue-compose-cancel ()
7472+
"Cancel the compose buffer.
7473+
7474+
Kills silently when the buffer is empty and unmodified; otherwise asks
7475+
for confirmation."
7476+
(interactive)
7477+
(unless agent-shell-queue-compose-mode
7478+
(user-error "Not in an agent-shell compose buffer"))
7479+
(if (or (and (zerop (buffer-size))
7480+
(not (buffer-modified-p)))
7481+
(y-or-n-p "Discard compose buffer? "))
7482+
(agent-shell-queue-compose--quit-or-kill)
7483+
(message "Kept draft")))
7484+
73517485
(provide 'agent-shell)
73527486

73537487
;;; agent-shell.el ends here

‎bin/test‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,30 @@ fi
2323
acp_root=${acp_root:-../../acp.el-plus/main}
2424
shell_maker_root=${shell_maker_root:-../../shell-maker/main}
2525

26+
# Default markdown_mode_root to the newest elpa-installed copy in the
27+
# user's Emacs config; override with the env var if checked out elsewhere.
28+
# Look in both the classic ~/.emacs.d/elpa and the XDG ~/.config/emacs/elpa
29+
# locations so users on either layout don't need to override.
30+
if [[ -z ${markdown_mode_root:-} ]]
31+
then
32+
# Pick the newest installed copy locally. Note: CI pins markdown-mode
33+
# to the Package-Requires floor (v2.5) — local runs may pick up a newer
34+
# version, so a passing local test isn't a guarantee CI will pass.
35+
shopt -s nullglob
36+
# Trailing slashes restrict the glob to directories — package archives
37+
# also drop sibling files (signatures, READMEs) into elpa/.
38+
elpa_markdown_dirs=(
39+
"$HOME"/.emacs.d/elpa/markdown-mode-*/
40+
"$HOME"/.config/emacs/elpa/markdown-mode-*/
41+
)
42+
shopt -u nullglob
43+
if (( 0 < ${#elpa_markdown_dirs[@]} ))
44+
then
45+
mapfile -t sorted_elpa < <(printf '%s\n' "${elpa_markdown_dirs[@]}" | sort -V)
46+
markdown_mode_root=${sorted_elpa[-1]%/}
47+
fi
48+
fi
49+
2650
die=0
2751
if ! [[ -r ${acp_root}/acp.el ]]; then
2852
echo "error: acp.el not found at ${acp_root}" >&2
@@ -36,6 +60,12 @@ if ! [[ -r ${shell_maker_root}/shell-maker.el ]]; then
3660
die=1
3761
fi
3862

63+
if ! [[ -r ${markdown_mode_root:-}/markdown-mode.el ]]; then
64+
echo "error: markdown-mode.el not found at ${markdown_mode_root:-<unset>}" >&2
65+
echo "Set markdown_mode_root to your markdown-mode checkout (>=2.5)" >&2
66+
die=1
67+
fi
68+
3969
if (( 0 < die )); then
4070
exit 1
4171
fi
@@ -44,6 +74,7 @@ fi
4474
mkdir -p deps
4575
ln -sfn "$(cd "${acp_root}" && pwd)" deps/acp.el
4676
ln -sfn "$(cd "${shell_maker_root}" && pwd)" deps/shell-maker
77+
ln -sfn "$(cd "${markdown_mode_root}" && pwd)" deps/markdown-mode
4778

4879
# Adapt a CI run block for local execution:
4980
# - Replace GitHub PR SHA context with local merge-base equivalents

0 commit comments

Comments
 (0)