Skip to content

Commit b9d8c59

Browse files
LughConor Nash
authored andcommitted
Add Emacs-only timestamp when agent returns control
Append a local HH:MM:SS timestamp to the chat buffer in --display-agent-end whenever a turn settles to idle (not on retry/continue). The timestamp is display-only: generated with format-time-string and inserted into the buffer, never sent to the pi CLI nor stored in canonical session history, so it is dropped on history rebuild/reload. Controlled by pi-coding-agent-show-return-timestamp (default t) and pi-coding-agent-return-timestamp-format, styled with the new pi-coding-agent-return-timestamp face.
1 parent 66d5b56 commit b9d8c59

3 files changed

Lines changed: 100 additions & 16 deletions

File tree

pi-coding-agent-render.el

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,24 @@ follow-up as a fresh prompt.")
616616
#'pi-coding-agent--drain-followup-queue-if-idle
617617
(current-buffer)))))
618618

619+
(defun pi-coding-agent--insert-return-timestamp ()
620+
"Append a local timestamp marking when the agent returned control.
621+
This decoration lives only in the Emacs chat buffer; it is never sent to
622+
the pi CLI nor persisted in session history. Controlled by
623+
`pi-coding-agent-show-return-timestamp' and
624+
`pi-coding-agent-return-timestamp-format'."
625+
(when pi-coding-agent-show-return-timestamp
626+
(let ((stamp (format-time-string pi-coding-agent-return-timestamp-format)))
627+
(pi-coding-agent--with-scroll-preservation
628+
(save-excursion
629+
(goto-char (point-max))
630+
(skip-chars-backward "\n")
631+
(delete-region (point) (point-max))
632+
(insert "\n\n"
633+
(propertize stamp 'face 'pi-coding-agent-return-timestamp
634+
'pi-coding-agent-return-timestamp t)
635+
"\n"))))))
636+
619637
(defun pi-coding-agent--display-agent-end ()
620638
"Finalize agent turn: normalize whitespace, handle abort, schedule queue."
621639
;; Reset per-turn state for clean next turn.
@@ -646,6 +664,11 @@ follow-up as a fresh prompt.")
646664
(insert "\n"))))
647665
(pi-coding-agent--set-activity-phase
648666
(if (eq pi-coding-agent--status 'sending) "thinking" "idle"))
667+
;; Control has returned to the user only when the turn settled to idle
668+
;; (not when pi will retry/continue). Stamp the local time then.
669+
(when (eq pi-coding-agent--status 'idle)
670+
(let ((inhibit-read-only t))
671+
(pi-coding-agent--insert-return-timestamp)))
649672
(pi-coding-agent--refresh-header)
650673
;; Give immediate post-run compaction/retry events a chance to arrive before
651674
;; turning a local follow-up into a new independent prompt.

pi-coding-agent-ui.el

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,13 +278,34 @@ still operate on the raw table source."
278278
:type 'boolean
279279
:group 'pi-coding-agent)
280280

281+
(defcustom pi-coding-agent-show-return-timestamp t
282+
"Whether to display a local timestamp when the agent returns control.
283+
When non-nil, each completed agent turn appends a timestamp line to the
284+
chat buffer marking when control returned to the user. This decoration
285+
is Emacs-only: it is never sent to the pi CLI nor stored in session
286+
history. The timestamp is formatted with
287+
`pi-coding-agent-return-timestamp-format'."
288+
:type 'boolean
289+
:group 'pi-coding-agent)
290+
291+
(defcustom pi-coding-agent-return-timestamp-format "%H:%M:%S"
292+
"`format-time-string' format for the agent-return timestamp.
293+
Used when `pi-coding-agent-show-return-timestamp' is non-nil."
294+
:type 'string
295+
:group 'pi-coding-agent)
296+
281297
;;;; Faces
282298

283299
(defface pi-coding-agent-timestamp
284300
'((t :inherit shadow))
285301
"Face for timestamps in message headers."
286302
:group 'pi-coding-agent)
287303

304+
(defface pi-coding-agent-return-timestamp
305+
'((t :inherit shadow :slant italic))
306+
"Face for the Emacs-only timestamp marking when the agent returned control."
307+
:group 'pi-coding-agent)
308+
288309
(defface pi-coding-agent-tool-name
289310
'((t :inherit font-lock-function-name-face :weight bold :slant italic))
290311
"Face for tool names (BASH, READ, etc.) in pi chat."

test/pi-coding-agent-render-test.el

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,47 @@ as the top-level structure."
142142
"agent_end normalizes trailing whitespace to single newline."
143143
(with-temp-buffer
144144
(pi-coding-agent-chat-mode)
145-
(pi-coding-agent--append-to-chat "Some response")
146-
(pi-coding-agent--display-agent-end)
147-
(should (string-suffix-p "response\n" (buffer-string)))))
145+
(let ((pi-coding-agent-show-return-timestamp nil))
146+
(pi-coding-agent--append-to-chat "Some response")
147+
(pi-coding-agent--display-agent-end)
148+
(should (string-suffix-p "response\n" (buffer-string))))))
149+
150+
(ert-deftest pi-coding-agent-test-return-timestamp-appended-when-idle ()
151+
"agent_end appends a local timestamp line when the turn settles to idle."
152+
(with-temp-buffer
153+
(pi-coding-agent-chat-mode)
154+
(let ((pi-coding-agent-show-return-timestamp t)
155+
(pi-coding-agent-return-timestamp-format "%H:%M:%S"))
156+
(setq pi-coding-agent--status 'idle)
157+
(pi-coding-agent--append-to-chat "Some response")
158+
(pi-coding-agent--display-agent-end)
159+
;; Trailing line is a HH:MM:SS timestamp carrying the return marker prop.
160+
(should (string-match-p "[0-9][0-9]:[0-9][0-9]:[0-9][0-9]\n\\'"
161+
(buffer-string)))
162+
(goto-char (point-max))
163+
(forward-line -1)
164+
(should (get-text-property (point)
165+
'pi-coding-agent-return-timestamp)))))
166+
167+
(ert-deftest pi-coding-agent-test-return-timestamp-suppressed-when-disabled ()
168+
"No timestamp is added when `pi-coding-agent-show-return-timestamp' is nil."
169+
(with-temp-buffer
170+
(pi-coding-agent-chat-mode)
171+
(let ((pi-coding-agent-show-return-timestamp nil))
172+
(setq pi-coding-agent--status 'idle)
173+
(pi-coding-agent--append-to-chat "Some response")
174+
(pi-coding-agent--display-agent-end)
175+
(should (string-suffix-p "response\n" (buffer-string))))))
176+
177+
(ert-deftest pi-coding-agent-test-return-timestamp-suppressed-when-retrying ()
178+
"No timestamp is added when the turn will continue (status not idle)."
179+
(with-temp-buffer
180+
(pi-coding-agent-chat-mode)
181+
(let ((pi-coding-agent-show-return-timestamp t))
182+
(setq pi-coding-agent--status 'sending)
183+
(pi-coding-agent--append-to-chat "Some response")
184+
(pi-coding-agent--display-agent-end)
185+
(should (string-suffix-p "response\n" (buffer-string))))))
148186

149187
(ert-deftest pi-coding-agent-test-spacing-blank-line-after-user-header ()
150188
"User header has a blank line after setext underline."
@@ -532,25 +570,27 @@ agent_end + next section's leading newline must not create triple newlines."
532570
;; Turn 1: user + assistant
533571
(pi-coding-agent--display-user-message "Hi")
534572
(pi-coding-agent--display-agent-start)
535-
(pi-coding-agent--display-message-delta "Hello!")
536-
(pi-coding-agent--render-complete-message)
537-
(pi-coding-agent--display-agent-end)
538-
;; Turn 2: user message
539-
(setq pi-coding-agent--assistant-header-shown nil)
540-
(pi-coding-agent--display-user-message "Bye")
541-
;; Should never have triple newlines (which would be two blank lines)
542-
(should-not (string-match-p "\n\n\n" (buffer-string)))))
573+
(let ((pi-coding-agent-show-return-timestamp nil))
574+
(pi-coding-agent--display-message-delta "Hello!")
575+
(pi-coding-agent--render-complete-message)
576+
(pi-coding-agent--display-agent-end)
577+
;; Turn 2: user message
578+
(setq pi-coding-agent--assistant-header-shown nil)
579+
(pi-coding-agent--display-user-message "Bye")
580+
;; Should never have triple newlines (which would be two blank lines)
581+
(should-not (string-match-p "\n\n\n" (buffer-string))))))
543582

544583
(ert-deftest pi-coding-agent-test-spacing-single-blank-line-before-compaction ()
545584
"Only one blank line between agent response and compaction header."
546585
(with-temp-buffer
547586
(pi-coding-agent-chat-mode)
548587
(pi-coding-agent--display-agent-start)
549-
(pi-coding-agent--display-message-delta "Some response.")
550-
(pi-coding-agent--render-complete-message)
551-
(pi-coding-agent--display-agent-end)
552-
(pi-coding-agent--display-compaction-result 50000 "Summary.")
553-
(should-not (string-match-p "\n\n\n" (buffer-string)))))
588+
(let ((pi-coding-agent-show-return-timestamp nil))
589+
(pi-coding-agent--display-message-delta "Some response.")
590+
(pi-coding-agent--render-complete-message)
591+
(pi-coding-agent--display-agent-end)
592+
(pi-coding-agent--display-compaction-result 50000 "Summary.")
593+
(should-not (string-match-p "\n\n\n" (buffer-string))))))
554594

555595
(ert-deftest pi-coding-agent-test-spacing-no-double-blank-between-tools ()
556596
"Consecutive tools have single blank line between them."

0 commit comments

Comments
 (0)