@@ -189,14 +189,36 @@ from either chat or input buffer."
189189 (message " Pi: New session started " ))
190190 (message " Pi: New session cancelled " )))))))
191191
192- (defun pi-coding-agent--session-dir-name (dir )
193- " Convert DIR to session directory name.
194- Matches pi's encoding: --path-with-dashes--.
195- Note: Handles both Unix and Windows path separators."
196- (let* ((clean-dir (directory-file-name dir)) ; Remove trailing slash
197- (safe-path (replace-regexp-in-string " [/\\\\ :]" " -"
198- (replace-regexp-in-string " ^[/\\\\ ]" " " clean-dir))))
199- (concat " --" safe-path " --" )))
192+ (defun pi-coding-agent--session-list-directory (&optional chat-buf )
193+ " Return the directory containing CHAT-BUF's current JSONL session file.
194+ Return nil when the current state has no usable session file. Relative
195+ session file names are resolved from the chat buffer's stable session
196+ directory."
197+ (let ((chat-buf (or chat-buf (pi-coding-agent--get-chat-buffer))))
198+ (when (and chat-buf (buffer-live-p chat-buf))
199+ (with-current-buffer chat-buf
200+ (when-let* ((session-file (plist-get pi-coding-agent--state
201+ :session-file ))
202+ ((stringp session-file))
203+ ((not (string-empty-p session-file))))
204+ (file-name-directory
205+ (expand-file-name session-file
206+ (pi-coding-agent--chat-session-directory
207+ chat-buf))))))))
208+
209+ (defun pi-coding-agent--with-session-list-directory (proc chat-buf callback )
210+ " Call CALLBACK with the session list directory for CHAT-BUF.
211+ When cached state has no session file, fetch fresh state from PROC first."
212+ (if-let* ((session-dir (pi-coding-agent--session-list-directory chat-buf)))
213+ (funcall callback session-dir)
214+ (pi-coding-agent--rpc-async proc '(:type " get_state" )
215+ (lambda (response )
216+ (when (and (plist-get response :success )
217+ (buffer-live-p chat-buf))
218+ (pi-coding-agent--apply-state-response chat-buf response))
219+ (when (buffer-live-p chat-buf)
220+ (funcall callback
221+ (pi-coding-agent--session-list-directory chat-buf)))))))
200222
201223(defun pi-coding-agent--session-metadata (path )
202224 " Extract metadata from session file PATH.
@@ -251,43 +273,43 @@ Call this from the chat buffer after switching or loading a session."
251273 (let ((metadata (pi-coding-agent--session-metadata session-file)))
252274 (setq pi-coding-agent--session-name (plist-get metadata :session-name )))))
253275
254- (defun pi-coding-agent--list-sessions (dir )
255- " List available session files for project DIR.
256- Returns list of absolute paths to .jsonl files, sorted by modification
257- time with most recently used first."
258- (let* ((sessions-base (expand-file-name " ~/.pi/agent/sessions/" ))
259- (session-dir (expand-file-name (pi-coding-agent--session-dir-name dir) sessions-base)))
260- (when (file-directory-p session-dir)
261- ; ; Sort by modification time descending (most recently used first)
262- (sort (directory-files session-dir t " \\ .jsonl$" )
276+ (defun pi-coding-agent--list-sessions (session-dir )
277+ " List valid session files in SESSION-DIR.
278+ Returns absolute paths to JSONL pi sessions, sorted by modification time with
279+ most recently used first."
280+ (when (and session-dir (file-directory-p session-dir))
281+ (let ((sessions (delq nil
282+ (mapcar (lambda (path )
283+ (and (pi-coding-agent--session-metadata path)
284+ path))
285+ (directory-files session-dir t
286+ " \\ .jsonl\\ '" )))))
287+ (sort sessions
263288 (lambda (a b )
264- (time-less-p (file-attribute-modification-time (file-attributes b))
265- (file-attribute-modification-time (file-attributes a))))))))
289+ (time-less-p (file-attribute-modification-time
290+ (file-attributes b))
291+ (file-attribute-modification-time
292+ (file-attributes a))))))))
266293
267294(defun pi-coding-agent--format-session-choice (path )
268295 " Format session PATH for display in selector.
269- Returns (display-string . path) for `completing-read' .
270- Prefers session name over first message when available."
271- (let ((metadata (pi-coding-agent--session-metadata path)))
272- (if metadata
273- (let* ((modified-time (plist-get metadata :modified-time ))
274- (session-name (plist-get metadata :session-name ))
275- (first-msg (plist-get metadata :first-message ))
276- (msg-count (plist-get metadata :message-count ))
277- (relative-time (pi-coding-agent--format-relative-time modified-time))
278- ; ; Prefer session name, fall back to first message preview
279- (label (cond
280- (session-name (pi-coding-agent--truncate-string session-name 50 ))
281- (first-msg (pi-coding-agent--truncate-string first-msg 50 ))
282- (t nil )))
283- (display (if label
284- (format " %s · %s (%d msgs)"
285- label relative-time msg-count)
286- (format " [empty session] · %s " relative-time))))
287- (cons display path))
288- ; ; Fallback to filename if metadata extraction fails
289- (let ((filename (file-name-nondirectory path)))
290- (cons filename path)))))
296+ Returns (display-string . path) for `completing-read' , or nil when PATH is not
297+ a valid pi session. Prefers session name over first message when available."
298+ (when-let* ((metadata (pi-coding-agent--session-metadata path)))
299+ (let* ((modified-time (plist-get metadata :modified-time ))
300+ (session-name (plist-get metadata :session-name ))
301+ (first-msg (plist-get metadata :first-message ))
302+ (msg-count (plist-get metadata :message-count ))
303+ (relative-time (pi-coding-agent--format-relative-time modified-time))
304+ (label (cond
305+ (session-name (pi-coding-agent--truncate-string session-name 50 ))
306+ (first-msg (pi-coding-agent--truncate-string first-msg 50 ))
307+ (t nil )))
308+ (display (if label
309+ (format " %s · %s (%d msgs)"
310+ label relative-time msg-count)
311+ (format " [empty session] · %s " relative-time))))
312+ (cons display path))))
291313
292314(defun pi-coding-agent--reset-session-state ()
293315 " Reset all session-specific state for a new session.
@@ -465,43 +487,65 @@ chat buffer from session history."
465487 (message " Pi: Failed to reload - %s "
466488 (or (plist-get response :error ) " unknown error" ))))))))))))
467489
490+ (defun pi-coding-agent--resume-selected-session (proc chat-buf selected-path )
491+ " Resume SELECTED-PATH using PROC and rebuild CHAT-BUF from its history."
492+ (pi-coding-agent--rpc-async
493+ proc
494+ (list :type " switch_session" :sessionPath selected-path)
495+ (lambda (response )
496+ (let* ((data (plist-get response :data ))
497+ (cancelled (plist-get data :cancelled )))
498+ (if (and (plist-get response :success )
499+ (pi-coding-agent--json-false-p cancelled))
500+ (progn
501+ (pi-coding-agent--refresh-session-state proc chat-buf selected-path)
502+ (pi-coding-agent--load-session-history
503+ proc
504+ (lambda (count )
505+ (message " Pi: Resumed session (%d messages) " count))
506+ chat-buf))
507+ (message " Pi: Failed to resume session " ))))))
508+
509+ (defun pi-coding-agent--resume-session-from-directory (proc chat-buf session-dir )
510+ " Prompt for a session from SESSION-DIR, then resume it using PROC.
511+ CHAT-BUF is rebuilt from the selected session history."
512+ (let ((sessions (pi-coding-agent--list-sessions session-dir)))
513+ (if (null sessions)
514+ (message " Pi: No previous sessions found " )
515+ (let* ((choices (delq nil
516+ (mapcar #'pi-coding-agent--format-session-choice
517+ sessions)))
518+ (choice-strings (mapcar #'car choices)))
519+ (if (null choices)
520+ (message " Pi: No previous sessions found " )
521+ (let* ((choice (completing-read
522+ " Resume session: "
523+ (lambda (string pred action )
524+ (if (eq action 'metadata )
525+ '(metadata (display-sort-function . identity))
526+ (complete-with-action action choice-strings
527+ string pred)))
528+ nil t ))
529+ (selected-path (cdr (assoc choice choices))))
530+ (when selected-path
531+ (pi-coding-agent--resume-selected-session
532+ proc chat-buf selected-path))))))))
533+
468534(defun pi-coding-agent-resume-session ()
469- " Resume a previous pi session from the current project ."
535+ " Resume a previous pi session stored beside the current session file ."
470536 (interactive )
471537 (when-let* ((proc (pi-coding-agent--get-process))
472- (dir (pi-coding-agent--session-directory))
473538 (chat-buf (pi-coding-agent--get-chat-buffer)))
474539 (when (pi-coding-agent--session-transition-ready-p chat-buf " resume" )
475- (let ((sessions (pi-coding-agent--list-sessions dir)))
476- (if (null sessions)
477- (message " Pi: No previous sessions found " )
478- (let* ((choices (mapcar #'pi-coding-agent--format-session-choice sessions))
479- (choice-strings (mapcar #'car choices))
480- (choice (completing-read " Resume session: "
481- (lambda (string pred action )
482- (if (eq action 'metadata )
483- '(metadata (display-sort-function . identity))
484- (complete-with-action action choice-strings string pred)))
485- nil t ))
486- (selected-path (cdr (assoc choice choices))))
487- (when selected-path
488- (pi-coding-agent--rpc-async
489- proc
490- (list :type " switch_session" :sessionPath selected-path)
491- (lambda (response )
492- (let* ((data (plist-get response :data ))
493- (cancelled (plist-get data :cancelled )))
494- (if (and (plist-get response :success )
495- (pi-coding-agent--json-false-p cancelled))
496- (progn
497- (pi-coding-agent--refresh-session-state
498- proc chat-buf selected-path)
499- (pi-coding-agent--load-session-history
500- proc
501- (lambda (count )
502- (message " Pi: Resumed session (%d messages) " count))
503- chat-buf))
504- (message " Pi: Failed to resume session " ))))))))))))
540+ (pi-coding-agent--with-session-list-directory
541+ proc chat-buf
542+ (lambda (session-dir )
543+ (cond
544+ ((not session-dir)
545+ (message " Pi: Session file not available " ))
546+ ((pi-coding-agent--session-transition-ready-p chat-buf " resume" )
547+ (pi-coding-agent--resume-session-from-directory
548+ proc chat-buf session-dir))))))))
505549
506550; ;;; Model and Thinking
507551
0 commit comments