diff options
Diffstat (limited to 'modules/ai-term-sessions.el')
| -rw-r--r-- | modules/ai-term-sessions.el | 50 |
1 files changed, 44 insertions, 6 deletions
diff --git a/modules/ai-term-sessions.el b/modules/ai-term-sessions.el index 99585a70..57d735d1 100644 --- a/modules/ai-term-sessions.el +++ b/modules/ai-term-sessions.el @@ -65,6 +65,20 @@ the start so names like \"foo agent [bar]\" do not match." (buffer-live-p buffer) (string-prefix-p cj/--ai-term-name-prefix (buffer-name buffer)))) +(defun cj/--ai-term-buffer-basename (buffer) + "Return the project basename embedded in BUFFER's AI-term name, or nil. + +The buffer name is \"agent [<basename>]\" (see +`cj/--ai-term-buffer-name') and never changes for the buffer's life, +unlike `default-directory', which ghostel retargets via OSC 7 every time +the shell cds. Teardown paths must key tmux-session lookups off this, +not the directory, or a close after a cd kills the wrong aiv- session. +Returns nil when BUFFER is not a live AI-term buffer." + (when (cj/--ai-term-buffer-p buffer) + (let ((name (buffer-name buffer))) + (when (string-suffix-p "]" name) + (substring name (length cj/--ai-term-name-prefix) -1))))) + (defun cj/--ai-term-agent-buffers () "Return the live AI-term buffers in `buffer-list' order. @@ -107,6 +121,23 @@ which the step materializes by attaching." (lambda (a b) (string< (cj/--ai-term-buffer-name a) (cj/--ai-term-buffer-name b)))))) +(defun cj/--ai-term-attached-agent-dirs () + "Return project dirs that have a live agent BUFFER (attached only). + +Like `cj/--ai-term-active-agent-dirs' but excludes detached tmux +sessions with no Emacs buffer -- this is the queue `cj/ai-term-next-attached' +\(M-SPC) steps through, so the fast chord stays among agents already on +screen. Detached sessions are reachable only via `cj/ai-term-next' +\(M-S-SPC). Sorted by agent buffer name for a stable rotation." + (let ((live-names (mapcar #'buffer-name (cj/--ai-term-agent-buffers)))) + (sort + (seq-filter + (lambda (dir) + (member (cj/--ai-term-buffer-name dir) live-names)) + (cj/--ai-term-candidates)) + (lambda (a b) + (string< (cj/--ai-term-buffer-name a) (cj/--ai-term-buffer-name b)))))) + (defun cj/--ai-term-tmux-session-name (dir) "Return the tmux session name for project directory DIR. @@ -157,7 +188,7 @@ looked up in SESSIONS, so the lossy whitespace->hyphen transform in `cj/--ai-term-tmux-session-name' never needs reversing." (and (member (cj/--ai-term-tmux-session-name dir) sessions) t)) -(defun cj/--ai-term-launch-command (dir) +(defun cj/--ai-term-launch-command (dir &optional agent-command) "Return the shell command line that runs the AI tool in a project tmux session. Uses `tmux new-session -A' so a second toggle on the same project reattaches @@ -167,9 +198,12 @@ comes from `cj/--ai-term-tmux-session-name'; the first window is named window auto-names after its command and the two read distinctly. The shell command run on first creation is - <cj/ai-term-agent-command>; exec bash + <agent command>; exec bash so the tmux window survives the AI command exiting -- the session stays -alive with a bare bash prompt for recovery, and reattach works the same way." +alive with a bare bash prompt for recovery, and reattach works the same way. +AGENT-COMMAND overrides `cj/ai-term-agent-command' for the fresh-session +case (the multi-backend picker passes the chosen runtime's command); on a +reattach `tmux new-session -A' ignores the command either way." (let ((session (cj/--ai-term-tmux-session-name dir)) (start-dir (expand-file-name dir))) ;; Pass the inner shell-command-string through `shell-quote-argument' @@ -184,7 +218,8 @@ alive with a bare bash prompt for recovery, and reattach works the same way." (shell-quote-argument cj/ai-term-tmux-window-name) (shell-quote-argument start-dir) (shell-quote-argument - (concat cj/ai-term-agent-command "; exec bash"))))) + (concat (or agent-command cj/ai-term-agent-command) + "; exec bash"))))) (defun cj/--ai-term-kill-tmux-session (session) "Kill the tmux SESSION via `tmux kill-session -t SESSION'. @@ -316,8 +351,11 @@ the metadata keeps the order ALIST was built in." (cycle-sort-function . identity)) (complete-with-action action alist string predicate)))) -(defun cj/--ai-term-pick-project () +(defun cj/--ai-term-pick-project (&optional sessions) "Prompt for an AI-agent project; return its absolute path. +SESSIONS, when non-nil, is a pre-fetched result of +`cj/--ai-term-live-tmux-sessions', so a caller that already paid for the +tmux subprocess can thread it through instead of spawning another. Candidates come from `cj/--ai-term-candidates', ordered by `cj/--ai-term-sort-candidates' so projects with a live tmux session @@ -333,7 +371,7 @@ Signals `user-error' when no candidates exist." (append cj/ai-term-project-roots cj/ai-term-container-roots) ", "))) - (let* ((sessions (cj/--ai-term-live-tmux-sessions)) + (let* ((sessions (or sessions (cj/--ai-term-live-tmux-sessions))) (sorted (cj/--ai-term-sort-candidates candidates sessions)) (display-alist (mapcar (lambda (p) |
