@@ -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' .
291294This 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.
311327Verification 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.
340356JSON 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 ))))))
0 commit comments