diff --git a/contrib/elisp/ellie.el b/contrib/elisp/ellie.el index 421cbbf..d375517 100644 --- a/contrib/elisp/ellie.el +++ b/contrib/elisp/ellie.el @@ -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." diff --git a/doc/ellie.md b/doc/ellie.md index 262ddc3..15fa4f5 100644 --- a/doc/ellie.md +++ b/doc/ellie.md @@ -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. +

+ ellie Emacs interface showing the Ollie session tree and chat window +

+ ## Session tree The tree shows all sessions organized by active/inactive state: diff --git a/doc/img/ellie.png b/doc/img/ellie.png new file mode 100644 index 0000000..10eeb28 Binary files /dev/null and b/doc/img/ellie.png differ