@@ -335,6 +335,9 @@ This ensures all files get code fences for consistent display."
335335
336336; ;; Buffer Linkage
337337
338+ (defvar-local pi-coding-agent-test--activity-marker nil
339+ " Buffer-local marker used by activity-phase hook tests." )
340+
338341(ert-deftest pi-coding-agent-test-input-buffer-finds-chat ()
339342 " Input buffer can find associated chat buffer."
340343 (pi-coding-agent-test-with-mock-session " /tmp/pi-coding-agent-test-link1/"
@@ -349,6 +352,164 @@ This ensures all files get code fences for consistent display."
349352 (should (eq (pi-coding-agent--get-input-buffer)
350353 (get-buffer " *pi-coding-agent-input:/tmp/pi-coding-agent-test-link2/*" ))))))
351354
355+ (ert-deftest pi-coding-agent-test-activity-phase-functions-receive-session-buffers ()
356+ " Activity phase functions receive buffers, phases, and reason."
357+ (let ((calls nil )
358+ (dir " /tmp/pi-coding-agent-test-activity-hook/" ))
359+ (pi-coding-agent-test-with-mock-session dir
360+ (let ((chat (get-buffer (pi-coding-agent--buffer-name :chat dir)))
361+ (input (get-buffer (pi-coding-agent--buffer-name :input dir)))
362+ (pi-coding-agent-activity-phase-functions
363+ (list (lambda (chat-buf input-buf old-phase new-phase reason )
364+ (push (list chat-buf input-buf old-phase new-phase reason)
365+ calls)))))
366+ (with-current-buffer chat
367+ (pi-coding-agent--set-activity-phase " thinking" )
368+ (pi-coding-agent--set-activity-phase " thinking" ))
369+ (should (= (length calls) 1 ))
370+ (pcase-let ((`(,seen-chat ,seen-input ,old-phase ,new-phase ,reason )
371+ (car calls)))
372+ (should (eq seen-chat chat))
373+ (should (eq seen-input input))
374+ (should (equal old-phase " idle" ))
375+ (should (equal new-phase " thinking" ))
376+ (should (eq reason 'phase-change )))))))
377+
378+ (ert-deftest pi-coding-agent-test-reset-session-state-forces-idle-activity-phase ()
379+ " Session reset applies idle even when user display state needs resync."
380+ (let ((calls nil )
381+ (dir " /tmp/pi-coding-agent-test-activity-reset/" ))
382+ (pi-coding-agent-test-with-mock-session dir
383+ (let ((chat (get-buffer (pi-coding-agent--buffer-name :chat dir)))
384+ (input (get-buffer (pi-coding-agent--buffer-name :input dir)))
385+ (pi-coding-agent-activity-phase-functions
386+ (list (lambda (chat-buf input-buf old-phase new-phase reason )
387+ (push (list chat-buf input-buf old-phase new-phase reason)
388+ calls)))))
389+ (with-current-buffer chat
390+ (pi-coding-agent--set-activity-phase " running" )
391+ (setq calls nil )
392+ (pi-coding-agent--reset-session-state))
393+ (should (= (length calls) 1 ))
394+ (pcase-let ((`(,seen-chat ,seen-input ,old-phase ,new-phase ,reason )
395+ (car calls)))
396+ (should (eq seen-chat chat))
397+ (should (eq seen-input input))
398+ (should (equal old-phase " running" ))
399+ (should (equal new-phase " idle" ))
400+ (should (eq reason 'reset )))))))
401+
402+ (ert-deftest pi-coding-agent-test-set-input-buffer-resyncs-activity-phase ()
403+ " Relinking an input buffer reapplies the current activity phase."
404+ (let ((calls nil )
405+ (dir " /tmp/pi-coding-agent-test-activity-relink/" )
406+ (new-input (generate-new-buffer " *pi-activity-relink-input*" )))
407+ (unwind-protect
408+ (pi-coding-agent-test-with-mock-session dir
409+ (let ((chat (get-buffer (pi-coding-agent--buffer-name :chat dir)))
410+ (pi-coding-agent-activity-phase-functions
411+ (list (lambda (chat-buf input-buf old-phase new-phase reason )
412+ (push (list chat-buf input-buf old-phase new-phase reason)
413+ calls)))))
414+ (with-current-buffer chat
415+ (pi-coding-agent--set-activity-phase " running" )
416+ (setq calls nil )
417+ (pi-coding-agent--set-input-buffer new-input))
418+ (pcase-let ((`(,seen-chat ,seen-input ,old-phase ,new-phase ,reason )
419+ (cl-find-if (lambda (call )
420+ (eq (cadr call) new-input))
421+ calls)))
422+ (should (eq seen-chat chat))
423+ (should (eq seen-input new-input))
424+ (should (equal old-phase " running" ))
425+ (should (equal new-phase " running" ))
426+ (should (eq reason 'input-link )))))
427+ (when (buffer-live-p new-input)
428+ (kill-buffer new-input)))))
429+
430+ (ert-deftest pi-coding-agent-test-set-input-buffer-clears-old-input-activity ()
431+ " Relinking input buffers lets hooks clean state from the old input."
432+ (let ((dir " /tmp/pi-coding-agent-test-activity-relink-cleanup/" )
433+ (new-input (generate-new-buffer " *pi-activity-relink-cleanup-input*" )))
434+ (unwind-protect
435+ (pi-coding-agent-test-with-mock-session dir
436+ (let ((chat (get-buffer (pi-coding-agent--buffer-name :chat dir)))
437+ (old-input (get-buffer (pi-coding-agent--buffer-name :input dir)))
438+ (pi-coding-agent-activity-phase-functions
439+ (list (lambda (_chat-buf input-buf _old-phase new-phase reason )
440+ (when (buffer-live-p input-buf)
441+ (with-current-buffer input-buf
442+ (cond
443+ ((eq reason 'input-unlink )
444+ (setq pi-coding-agent-test--activity-marker nil ))
445+ ((not (equal new-phase " idle" ))
446+ (setq pi-coding-agent-test--activity-marker t )))))))))
447+ (with-current-buffer chat
448+ (pi-coding-agent--set-activity-phase " running" ))
449+ (with-current-buffer old-input
450+ (should pi-coding-agent-test--activity-marker))
451+ (with-current-buffer chat
452+ (pi-coding-agent--set-input-buffer new-input))
453+ (with-current-buffer old-input
454+ (should-not pi-coding-agent-test--activity-marker))
455+ (with-current-buffer new-input
456+ (should pi-coding-agent-test--activity-marker))))
457+ (when (buffer-live-p new-input)
458+ (kill-buffer new-input)))))
459+
460+ (ert-deftest pi-coding-agent-test-activity-phase-reason-distinguishes-relink-from-idle ()
461+ " Relinking input buffers does not look like a real idle transition."
462+ (let ((finished-notifications 0 )
463+ (dir " /tmp/pi-coding-agent-test-activity-relink-reason/" )
464+ (new-input (generate-new-buffer " *pi-activity-relink-reason-input*" )))
465+ (unwind-protect
466+ (pi-coding-agent-test-with-mock-session dir
467+ (let ((chat (get-buffer (pi-coding-agent--buffer-name :chat dir)))
468+ (pi-coding-agent-activity-phase-functions
469+ (list (lambda (_chat-buf _input-buf old-phase new-phase reason )
470+ (when (and (eq reason 'phase-change )
471+ (not (equal old-phase " idle" ))
472+ (equal new-phase " idle" ))
473+ (setq finished-notifications
474+ (1+ finished-notifications)))))))
475+ (with-current-buffer chat
476+ (pi-coding-agent--set-activity-phase " running" )
477+ (pi-coding-agent--set-input-buffer new-input))
478+ (should (= finished-notifications 0 ))
479+ (with-current-buffer chat
480+ (pi-coding-agent--set-activity-phase " idle" ))
481+ (should (= finished-notifications 1 ))))
482+ (when (buffer-live-p new-input)
483+ (kill-buffer new-input)))))
484+
485+ (ert-deftest pi-coding-agent-test-chat-buffer-kill-forces-teardown-activity-phase ()
486+ " Killing a chat buffer applies idle with teardown as the reason."
487+ (let ((calls nil )
488+ (dir " /tmp/pi-coding-agent-test-activity-teardown/" ))
489+ (pi-coding-agent-test-with-mock-session dir
490+ (let ((chat (get-buffer (pi-coding-agent--buffer-name :chat dir)))
491+ (input (get-buffer (pi-coding-agent--buffer-name :input dir)))
492+ (pi-coding-agent-activity-phase-functions
493+ (list (lambda (chat-buf input-buf old-phase new-phase reason )
494+ (push (list chat-buf input-buf old-phase new-phase reason)
495+ calls)))))
496+ (with-current-buffer chat
497+ (pi-coding-agent--set-activity-phase " running" ))
498+ (setq calls nil )
499+ (kill-buffer chat)
500+ (pcase-let ((`(,seen-chat ,seen-input ,old-phase ,new-phase ,reason )
501+ (cl-find-if (lambda (call )
502+ (and (eq (nth 4 call) 'teardown )
503+ (equal (nth 2 call) " running" )
504+ (equal (nth 3 call) " idle" )))
505+ calls)))
506+ (should (eq seen-chat chat))
507+ (should (or (null seen-input)
508+ (eq seen-input input)))
509+ (should (equal old-phase " running" ))
510+ (should (equal new-phase " idle" ))
511+ (should (eq reason 'teardown )))))))
512+
352513(ert-deftest pi-coding-agent-test-get-process-from-chat ()
353514 " Can get process from chat buffer."
354515 (let ((default-directory " /tmp/pi-coding-agent-test-proc1/" )
0 commit comments