ellie: point preservation, stale-stream guard, block-headers, keybinding polish, and doc screenshot
ellie.el: - Preserve tree selection and cursor across tree rerenders. - Keep chat cursor stationary; only update when already at end. - Guard chat-filter against stale output from replaced ollie-9p read processes. - Render [[[name]]] markers as bold underlined block headers; suppress [[[end]]]. - Refresh session/idx and start timer on startup so tree displays immediately. - Fix Active/Inactive header tuple layout (was missing 'nil' slot). - Bind TAB for tree expand/collapse, C-c C-SPC and C-c C-p for prompt input, SPC in chat for prompt, q in chat for quit (stops stream + buries buffer). - RET on an agent now opens the chat buffer. - Prompt input window selects and focuses itself after opening. doc/ellie.md: - Embed ellie screenshot (img/ellie.png) centered at 75% width above Session tree.
This commit is contained in:
parent
7888627e90
commit
6fe9c20b54
|
|
@ -394,7 +394,7 @@ Prompts for agent profile and other options."
|
|||
(setq ellie--tree-expanded '("active"))
|
||||
(dolist (s sessions)
|
||||
(push (nth 2 s) ellie--tree-expanded)))
|
||||
(push (list 'header "active" "Active") result)
|
||||
(push (list 'header "active" nil "Active") result)
|
||||
(when (and active (member "active" ellie--tree-expanded))
|
||||
(dolist (s active)
|
||||
(let ((name (nth 1 s)) (id (nth 2 s)) (agents (nth 3 s)))
|
||||
|
|
@ -409,7 +409,7 @@ Prompts for agent profile and other options."
|
|||
result)))))))
|
||||
;; Inactive header
|
||||
(when inactive
|
||||
(push (list 'header "inactive" "Inactive") result)
|
||||
(push (list 'header "inactive" nil "Inactive") result)
|
||||
(when (member "inactive" ellie--tree-expanded)
|
||||
(dolist (s inactive)
|
||||
(let ((name (nth 1 s)) (id (nth 2 s)) (agents (nth 3 s)))
|
||||
|
|
@ -486,7 +486,7 @@ Prompts for agent profile and other options."
|
|||
(setq ellie--session-id parent
|
||||
ellie--agent-id key)
|
||||
(ellie--persist-session parent)
|
||||
(message "Agent: %s / %s" parent key)))))
|
||||
(ellie-tree-chat)))))
|
||||
|
||||
(defun ellie-tree-chat ()
|
||||
"Open chat buffer for the selected session/agent."
|
||||
|
|
@ -555,15 +555,35 @@ Prompts for agent profile and other options."
|
|||
(defvar ellie--chat-stream nil
|
||||
"Process object for the chat stream, or nil.")
|
||||
|
||||
(defun ellie--chat-filter (_proc output)
|
||||
(defun ellie--render-chat-output (output)
|
||||
"Render block markers in OUTPUT as block headers.
|
||||
Suppress `[[[end]]]` markers."
|
||||
(let ((start 0)
|
||||
rendered)
|
||||
(while (string-match "\\[\\[\\[\\([^]]+\\)\\]\\]\\]" output start)
|
||||
(push (substring output start (match-beginning 0)) rendered)
|
||||
(let ((name (match-string 1 output)))
|
||||
(unless (string= name "end")
|
||||
(push (propertize (concat name "\n")
|
||||
'face '(:weight bold :underline t))
|
||||
rendered)))
|
||||
(setq start (match-end 0)))
|
||||
(apply #'concat (nreverse (cons (substring output start) rendered)))))
|
||||
|
||||
(defun ellie--chat-filter (proc output)
|
||||
"Filter function for the chat stream process.
|
||||
Inserts OUTPUT into the chat buffer as it arrives."
|
||||
(when (buffer-live-p (get-buffer ellie--buf))
|
||||
Ignore output from a stream that has already been replaced."
|
||||
(when (and (eq proc ellie--chat-stream)
|
||||
(buffer-live-p (get-buffer ellie--buf)))
|
||||
(with-current-buffer ellie--buf
|
||||
(let ((inhibit-read-only t)
|
||||
(at-end (= (point) (point-max))))
|
||||
(insert output)
|
||||
(ansi-color-apply-on-region (point-marker) (point))
|
||||
(at-end (= (point) (point-max)))
|
||||
(start (copy-marker (point-max) t)))
|
||||
(save-excursion
|
||||
(goto-char (point-max))
|
||||
(insert (ellie--render-chat-output output))
|
||||
(ansi-color-apply-on-region start (point)))
|
||||
(set-marker start nil)
|
||||
(font-lock-ensure)
|
||||
(when at-end (goto-char (point-max))))
|
||||
(force-mode-line-update))))
|
||||
|
|
@ -614,9 +634,13 @@ QUEUED non-nil means the prompt will be enqueued, not submitted immediately."
|
|||
(erase-buffer)
|
||||
(ellie-input-mode)
|
||||
(setq ellie--input-queued queued))
|
||||
(display-buffer buf
|
||||
`(display-buffer-below-selected
|
||||
(window-height . ,ellie-input-window-height)))))
|
||||
(let ((window (display-buffer buf
|
||||
`(display-buffer-below-selected
|
||||
(window-height . ,ellie-input-window-height)))))
|
||||
(when (window-live-p window)
|
||||
(select-window window)
|
||||
(with-current-buffer buf
|
||||
(goto-char (point-max)))))))
|
||||
|
||||
(defun ellie-input-submit ()
|
||||
"Submit the contents of the input buffer as a prompt."
|
||||
|
|
@ -645,6 +669,7 @@ QUEUED non-nil means the prompt will be enqueued, not submitted immediately."
|
|||
(defvar ellie-mode-map
|
||||
(let ((m (make-sparse-keymap)))
|
||||
(define-key m (kbd "RET") #'ellie-send-input)
|
||||
(define-key m (kbd "SPC") #'ellie-send-input)
|
||||
(define-key m (kbd "C-c C-q") #'ellie-queue-input)
|
||||
(define-key m (kbd "C-c C-c") #'ellie-stop)
|
||||
(define-key m (kbd "C-c C-k") #'ellie-kill-session)
|
||||
|
|
@ -653,7 +678,7 @@ QUEUED non-nil means the prompt will be enqueued, not submitted immediately."
|
|||
(define-key m (kbd "C-c C-p") #'ellie-show-system-prompt)
|
||||
(define-key m (kbd "n") #'ellie-new-and-open)
|
||||
(define-key m (kbd "g") #'ellie-refresh-chat)
|
||||
(define-key m (kbd "q") #'ellie-tree)
|
||||
(define-key m (kbd "q") #'ellie-quit-chat)
|
||||
m)
|
||||
"Keymap for `ellie-mode'.")
|
||||
|
||||
|
|
@ -667,6 +692,12 @@ QUEUED non-nil means the prompt will be enqueued, not submitted immediately."
|
|||
(interactive)
|
||||
(ellie--open-input t))
|
||||
|
||||
(defun ellie-quit-chat ()
|
||||
"Stop the chat stream and bury the chat buffer."
|
||||
(interactive)
|
||||
(ellie--stop-watching)
|
||||
(bury-buffer (get-buffer ellie--buf)))
|
||||
|
||||
(defun ellie-refresh-chat ()
|
||||
"Restart the chat stream."
|
||||
(interactive)
|
||||
|
|
@ -740,13 +771,15 @@ QUEUED non-nil means the prompt will be enqueued, not submitted immediately."
|
|||
(message "Attached to session %s" (car last)))
|
||||
(setq ellie--session-id nil
|
||||
ellie--agent-id nil))
|
||||
(ellie--tree-render))
|
||||
(ellie--tree-render)
|
||||
(run-at-time 0.5 nil #'ellie-tree-refresh))
|
||||
|
||||
;;;###autoload
|
||||
(defun ellie-tree ()
|
||||
"Open the session tree buffer."
|
||||
(interactive)
|
||||
(ellie--tree-render))
|
||||
(ellie--tree-render)
|
||||
(run-at-time 0.5 nil #'ellie-tree-refresh))
|
||||
|
||||
(defun ellie-new-and-open ()
|
||||
"Create a new session and open the chat buffer."
|
||||
|
|
|
|||
|
|
@ -44,6 +44,10 @@ M-x ellie
|
|||
Opens the session tree (`*ellie-sessions*`), attached to the last-known
|
||||
session for your working directory if it still exists.
|
||||
|
||||
<p align="center">
|
||||
<img src="img/ellie.png" alt="ellie Emacs interface showing the Ollie session tree and chat window" width="75%">
|
||||
</p>
|
||||
|
||||
## Session tree
|
||||
|
||||
The tree shows all sessions organized by active/inactive state:
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 103 KiB |
Loading…
Reference in New Issue