Skip to content

Commit 049d6b9

Browse files
Respect current Pi compaction and queued user input (#213)
* Align with current Pi core package and events Use the current @earendil-works/pi-coding-agent package for real integration tests, pinned to the latest 0.74.x patch baseline. Handle current compaction_start/compaction_end RPC events while preserving legacy auto_compaction_* aliases for older Pi binaries. Update compaction tests to exercise the canonical event names and keep alias coverage explicit. * Handle modern compaction completion once * Preserve prompt order around compaction retry * Align runtime and CI with Pi 0.75.5 * Stabilize prompt recovery around compaction * Stabilize prompt busy-state edges --------- Co-authored-by: NicabarNimble <nicabar@gmail.com>
1 parent a23785b commit 049d6b9

16 files changed

Lines changed: 2414 additions & 232 deletions

.github/workflows/nightly.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ jobs:
5555
version: '30.1'
5656
- uses: actions/setup-node@v4
5757
with:
58-
node-version: '20'
58+
node-version: '24'
5959

6060
- name: Get pi version
6161
run: |

.github/workflows/test-integration.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
version: '30.1'
3535
- uses: actions/setup-node@v4
3636
with:
37-
node-version: '20'
37+
node-version: '24'
3838
- name: Setup Ollama model
3939
run: |
4040
# Wait for Ollama to be ready

Makefile

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ BATCH = $(EMACS) --batch -Q -L . \
77
LOCAL_LOAD_PATH = --eval "(setq load-path (cons (expand-file-name \".\") load-path))"
88

99
# Pi CLI version — single source of truth (workflows extract this automatically)
10-
PI_VERSION ?= 0.65.0
10+
PI_VERSION ?= 0.75.5
11+
PI_PACKAGE ?= @earendil-works/pi-coding-agent
1112
PI_BIN ?= .cache/pi/node_modules/.bin/pi
1213
PI_BIN_DIR = $(abspath $(dir $(PI_BIN)))
1314

@@ -143,16 +144,16 @@ install-hooks:
143144

144145
setup-pi:
145146
@if [ -x "$(PI_BIN)" ]; then \
146-
CURRENT=$$($(PI_BIN) --version 2>/dev/null); \
147+
CURRENT=$$($(PI_BIN) --version 2>&1 | tr -d '\r' | grep -Eo '^[0-9]+[.][0-9]+[.][0-9]+' | tail -1); \
147148
if [ "$$CURRENT" != "$(PI_VERSION)" ] && [ "$(PI_VERSION)" != "latest" ]; then \
148149
echo "Cached pi@$$CURRENT differs from requested $(PI_VERSION), reinstalling..."; \
149150
rm -rf .cache/pi; \
150151
fi; \
151152
fi
152153
@if [ ! -x "$(PI_BIN)" ]; then \
153-
echo "Installing pi@$(PI_VERSION) to .cache/pi/..."; \
154+
echo "Installing $(PI_PACKAGE)@$(PI_VERSION) to .cache/pi/..."; \
154155
rm -rf .cache/pi; \
155-
npm install --prefix .cache/pi @mariozechner/pi-coding-agent@$(PI_VERSION) --silent; \
156+
npm install --prefix .cache/pi --ignore-scripts $(PI_PACKAGE)@$(PI_VERSION) --silent; \
156157
fi
157158
@echo "Using pi: $(PI_BIN)"
158159
@$(PI_BIN) --version || (echo "ERROR: pi not working"; exit 1)

README.org

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ shortest path to a usable session:
1919

2020
#+begin_src bash
2121
# 1. Install the pi CLI
22-
npm install -g @mariozechner/pi-coding-agent
22+
npm install -g @earendil-works/pi-coding-agent@0.75.5
2323

2424
# Or with mise
25-
mise use -g npm:@mariozechner/pi-coding-agent
25+
mise use -g npm:@earendil-works/pi-coding-agent@0.75.5
2626

2727
# 2. Authenticate the CLI
2828
pi --login
@@ -65,7 +65,8 @@ needed for markdown rendering. That requires a C compiler (=gcc= or =cc=).
6565
* Requirements
6666

6767
- Emacs 29.1 or later (with tree-sitter support)
68-
- [[https://pi.dev][pi coding agent]] 0.65.0 or later, installed and in PATH
68+
- Node.js 22.19 or later for the pi CLI; Node 24 is used in CI
69+
- [[https://pi.dev][pi coding agent]] =@earendil-works/pi-coding-agent@0.75.5= or later, installed and in PATH
6970
- pi CLI authentication via =pi --login= or provider API keys
7071
- A C compiler (=gcc= or =cc=) for installing tree-sitter grammars
7172

pi-coding-agent-core.el

Lines changed: 80 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -241,14 +241,17 @@ containing EVENT, then clears this process's pending request tables."
241241
(when stderr
242242
(list :stderr stderr)))))
243243
(unwind-protect
244-
(progn
245-
(when pending
246-
(maphash (lambda (_id callback)
247-
(funcall callback error-response))
248-
pending)
249-
(clrhash pending))
250-
(when pending-types
251-
(clrhash pending-types)))
244+
(unwind-protect
245+
(progn
246+
(when pending
247+
(maphash (lambda (_id callback)
248+
(funcall callback error-response))
249+
pending)
250+
(clrhash pending))
251+
(when pending-types
252+
(clrhash pending-types)))
253+
(when-let* ((handler (process-get proc 'pi-coding-agent-exit-handler)))
254+
(funcall handler error-response)))
252255
(pi-coding-agent--cleanup-process-stderr-buffer proc))))
253256

254257
(defvar pi-coding-agent-executable) ; forward decl — core.el cannot require ui.el
@@ -287,14 +290,27 @@ Returns the process object."
287290

288291
(defvar-local pi-coding-agent--status 'idle
289292
"Current status of the pi session (buffer-local in chat buffer).
290-
One of: `idle', `streaming', `compacting'.
293+
One of: `idle', `sending', `streaming', `compacting'.
291294
This is the single source of truth for session activity state.
292295
293-
Status transitions are driven by events from pi:
294-
- `idle' -> `streaming' on agent_start
295-
- `streaming' -> `idle' on agent_end
296-
- `idle' -> `compacting' on auto_compaction_start
297-
- `compacting' -> `idle' on auto_compaction_end")
296+
Runtime status transitions are driven by events from pi:
297+
- `idle' or `sending' -> `streaming' on agent_start
298+
- `streaming' -> `sending' on agent_end with willRetry
299+
- `streaming' -> `idle' on agent_end without retry
300+
- `idle' -> `compacting' on compaction_start
301+
- `compacting' -> `sending' on successful compaction_end with willRetry
302+
- `compacting' -> `sending' on successful compaction_end that resumes
303+
prompt preflight
304+
- `compacting' -> `idle' on compaction_end without retry, failure, or abort
305+
306+
Local commands may mark a session busy before the first event arrives,
307+
for example normal prompt submission and manual compaction during the
308+
RPC pre-event window.")
309+
310+
(defvar-local pi-coding-agent--pre-compaction-status nil
311+
"Status that was active before a compaction event sequence.
312+
Used to restore local prompt submission state when Pi compacts during prompt
313+
preflight before the agent turn has started.")
298314

299315
(defvar-local pi-coding-agent--state nil
300316
"Current state of the pi session (buffer-local in chat buffer).
@@ -310,7 +326,7 @@ A plist with keys like :model, :thinking-level, :messages, etc.")
310326
"Return t if state should be verified with get_state.
311327
Verification is needed when:
312328
- State and timestamp exist
313-
- Session is idle (not streaming or compacting)
329+
- Session status is `idle'
314330
- Timestamp is older than `pi-coding-agent--state-verify-interval' seconds."
315331
(and pi-coding-agent--state
316332
pi-coding-agent--state-timestamp
@@ -340,10 +356,33 @@ Use when reading JSON fields that may be null or string.
340356
JSON null (:null) and non-strings become nil."
341357
(and (stringp value) value))
342358

359+
(defun pi-coding-agent--compaction-result-from-event (event)
360+
"Return EVENT's successful compaction result, or nil when absent."
361+
(let ((result (plist-get event :result)))
362+
(unless (or (null result)
363+
(pi-coding-agent--json-null-p result))
364+
result)))
365+
366+
(defun pi-coding-agent--compaction-end-success-p (event)
367+
"Return non-nil when EVENT reports a completed, non-aborted compaction."
368+
(and (not (pi-coding-agent--normalize-boolean (plist-get event :aborted)))
369+
(not (null (pi-coding-agent--compaction-result-from-event event)))))
370+
371+
(defun pi-coding-agent--compaction-end-will-retry-p (event)
372+
"Return non-nil when EVENT indicates Pi will retry after compaction.
373+
A retry is only considered pending for a successful compaction result;
374+
failed or aborted compactions must not leave the session busy."
375+
(and (pi-coding-agent--normalize-boolean (plist-get event :willRetry))
376+
(pi-coding-agent--compaction-end-success-p event)))
377+
378+
(defun pi-coding-agent--compaction-end-resumes-preflight-p (event)
379+
"Return non-nil when EVENT can resume a pre-compaction prompt."
380+
(and (eq pi-coding-agent--pre-compaction-status 'sending)
381+
(pi-coding-agent--compaction-end-success-p event)))
382+
343383
(defun pi-coding-agent--update-state-from-event (event)
344384
"Update status and state based on EVENT.
345-
Handles agent lifecycle, message events, and error/retry events.
346-
Sets status to `streaming' on agent_start, `idle' on agent_end."
385+
Handles agent lifecycle, message events, compaction, and error/retry events."
347386
(let ((type (plist-get event :type)))
348387
(pcase type
349388
("agent_start"
@@ -352,7 +391,10 @@ Sets status to `streaming' on agent_start, `idle' on agent_end."
352391
(plist-put pi-coding-agent--state :last-error nil)
353392
(setq pi-coding-agent--state-timestamp (float-time)))
354393
("agent_end"
355-
(setq pi-coding-agent--status 'idle)
394+
(setq pi-coding-agent--status
395+
(if (pi-coding-agent--normalize-boolean (plist-get event :willRetry))
396+
'sending
397+
'idle))
356398
(plist-put pi-coding-agent--state :is-retrying nil)
357399
(plist-put pi-coding-agent--state :messages (plist-get event :messages))
358400
(setq pi-coding-agent--state-timestamp (float-time)))
@@ -366,13 +408,33 @@ Sets status to `streaming' on agent_start, `idle' on agent_end."
366408
(pi-coding-agent--handle-tool-update event))
367409
("tool_execution_end"
368410
(pi-coding-agent--handle-tool-end event))
411+
("compaction_start"
412+
(setq pi-coding-agent--pre-compaction-status
413+
(unless (eq pi-coding-agent--status 'compacting)
414+
pi-coding-agent--status))
415+
(setq pi-coding-agent--status 'compacting)
416+
(setq pi-coding-agent--state-timestamp (float-time)))
417+
("compaction_end"
418+
(setq pi-coding-agent--status
419+
(cond
420+
((pi-coding-agent--compaction-end-will-retry-p event)
421+
'sending)
422+
((pi-coding-agent--compaction-end-resumes-preflight-p event)
423+
'sending)
424+
(t
425+
'idle)))
426+
(setq pi-coding-agent--pre-compaction-status nil)
427+
(setq pi-coding-agent--state-timestamp (float-time)))
369428
("auto_retry_start"
429+
(setq pi-coding-agent--status 'sending)
370430
(plist-put pi-coding-agent--state :is-retrying t)
371431
(plist-put pi-coding-agent--state :retry-attempt (plist-get event :attempt))
372432
(plist-put pi-coding-agent--state :last-error (plist-get event :errorMessage)))
373433
("auto_retry_end"
374434
(plist-put pi-coding-agent--state :is-retrying nil)
375435
(unless (eq (plist-get event :success) t)
436+
(when (eq pi-coding-agent--status 'sending)
437+
(setq pi-coding-agent--status 'idle))
376438
(plist-put pi-coding-agent--state :last-error (plist-get event :finalError))))
377439
("extension_error"
378440
(plist-put pi-coding-agent--state :last-error (plist-get event :error))))))

pi-coding-agent-input.el

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -303,13 +303,15 @@ The /compact command is handled locally; other slash commands sent to pi."
303303
(interactive)
304304
(let* ((text (string-trim (buffer-string)))
305305
(chat-buf (pi-coding-agent--get-chat-buffer))
306-
(status (and chat-buf (buffer-local-value 'pi-coding-agent--status chat-buf)))
307-
(busy (and status (memq status '(streaming sending compacting)))))
306+
(busy (and chat-buf (pi-coding-agent--session-busy-p chat-buf))))
308307
(cond
309308
((string-empty-p text) nil)
309+
((and busy (pi-coding-agent--builtin-command-text-p text))
310+
(message "Pi: Cannot queue /%s while Pi is busy"
311+
(pi-coding-agent--builtin-command-name text)))
310312
(busy
311313
(pi-coding-agent--queue-followup-text chat-buf text)
312-
(message "Pi: Message queued (will send after current response)"))
314+
(message "Pi: Message queued (will send when Pi is ready)"))
313315
(t
314316
(pi-coding-agent--accept-input-text text)
315317
(with-current-buffer chat-buf
@@ -542,23 +544,29 @@ it back via message_start at the correct position (after current
542544
assistant output completes).
543545
544546
When compaction is in progress, steering text is queued as a local
545-
follow-up and sent after compaction completes."
547+
follow-up. It is sent after non-retry compaction, or after Pi's
548+
automatic overflow retry turn finishes."
546549
(interactive)
547550
(let ((text (string-trim (buffer-string))))
548551
(unless (string-empty-p text)
549552
(let ((chat-buf (pi-coding-agent--get-chat-buffer)))
550553
(when chat-buf
551554
(let ((status (buffer-local-value 'pi-coding-agent--status chat-buf)))
552555
(cond
553-
((eq status 'idle)
556+
((and (eq status 'idle)
557+
(not (pi-coding-agent--session-busy-p chat-buf)))
554558
(message "Pi: Nothing to interrupt - use C-c C-c to send"))
555-
((eq status 'compacting)
559+
((or (eq status 'compacting)
560+
(and (eq status 'idle)
561+
(pi-coding-agent--session-busy-p chat-buf)))
556562
(pi-coding-agent--queue-followup-text chat-buf text)
557-
(message "Pi: Steering queued (will send after compaction)"))
558-
(t
563+
(message "Pi: Steering queued (will send when Pi is ready)"))
564+
((memq status '(sending streaming))
559565
(when (pi-coding-agent--send-steer-message text)
560566
(pi-coding-agent--accept-input-text text)
561-
(message "Pi: Steering message sent"))))))))))
567+
(message "Pi: Steering message sent")))
568+
(t
569+
(message "Pi: Cannot steer while session status is %s" status)))))))))
562570

563571
(defun pi-coding-agent-queue-followup ()
564572
"Queue current input as a follow-up message.

0 commit comments

Comments
 (0)