view-input now stores cursor-line and cursor-col in state after word-wrapping. position-cursor uses these to place the cursor on the correct wrapped line, instead of the hardcoded row (- h 6) which always put the cursor on the first line.
434 lines
20 KiB
Org Mode
434 lines
20 KiB
Org Mode
#+TITLE: Passepartout TUI — Model
|
|
#+PROPERTY: header-args:lisp :tangle /home/user/.local/share/passepartout/lisp/channel-tui-state.lisp
|
|
|
|
* Model
|
|
|
|
The TUI state is a single plist accessed via ~st~ / ~(setf st)~.
|
|
All state mutation flows through event handlers in the controller.
|
|
|
|
** Contract
|
|
|
|
1. (init-state): returns a fresh state plist with ~:msgs~ list,
|
|
~:input~ buffer, ~:dirty~ flag, ~:busy~ flag, and ~:connection~ status.
|
|
2. (add-msg role content &key gate-trace): appends a message object
|
|
to the ~:messages~ vector (v0.3.3), tagged with timestamp, role,
|
|
and optional gate-trace from the daemon (v0.4.0).
|
|
3. (queue-event ev): thread-safely enqueues an event for the
|
|
reader loop. (drain-queue) returns and clears the queue.
|
|
|
|
** Package + State
|
|
#+BEGIN_SRC lisp :tangle /home/user/.local/share/passepartout/lisp/channel-tui-state.lisp
|
|
(defpackage :passepartout.channel-tui
|
|
(:use :cl :passepartout :usocket :bordeaux-threads)
|
|
(:export :tui-main :st :add-msg :now :input-string
|
|
:queue-event :drain-queue :init-state
|
|
:view-status :view-chat :view-input :redraw
|
|
:position-cursor
|
|
:input-panel-top
|
|
:on-key :on-daemon-msg :send-daemon
|
|
:connect-daemon :disconnect-daemon
|
|
:*tui-theme* :theme-color))
|
|
(in-package :passepartout.channel-tui)
|
|
|
|
(defvar *state* nil)
|
|
(defvar *event-queue* nil)
|
|
(defvar *event-lock* (bt:make-lock "tui-event-lock"))
|
|
|
|
(defvar *tui-theme*
|
|
'( :user-fg "#fab283" :user-bg "#1e1e1e" :user-border "#fab283"
|
|
:agent-border "#c0a080" :agent-header "#d4956a" :agent-fg "#e8e8e8"
|
|
:system "#808080"
|
|
:input-prompt "#fab283" :input-fg "#e8e8e8"
|
|
:hint "#606060"
|
|
:status-bg "#141414" :status-fg "#e8e8e8"
|
|
:bg "#0a0a0a" :bg-panel "#141414" :bg-element "#1e1e1e"
|
|
:text-muted "#808080"
|
|
:dot-connected "#7fd88f" :dot-disconnected "#e06c75"
|
|
:bg-input "#2e2e2e"
|
|
:error "#e06c75"
|
|
:tool-running "#fab283" :tool-done "#7fd88f" :tool-error "#e06c75"
|
|
:thinking-bg "#3a3a3a" :symbolic-border "#707070"
|
|
:separator "#3c3c3c" :accent "#fab283" :dim "#606060")
|
|
"Dark-neutral color theme with warm amber accent. Backgrounds are dark grays,
|
|
semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
|
|
:text-muted, :user-fg/bg/border, :agent-border/header/fg, :system,
|
|
:input-prompt/fg, :hint, :status-bg/fg, :bg-input, :thinking-bg,
|
|
:symbolic-border, :dot-connected/disconnected, :error, :tool-*,
|
|
:separator, :accent, :dim.")
|
|
|
|
(defvar *tui-theme-presets*
|
|
'(:amber
|
|
(:user-fg "#fab283" :user-bg "#1e1e1e" :user-border "#fab283"
|
|
:agent-header "#d4956a" :agent-fg "#e8e8e8"
|
|
:agent-border "#c0a080" :thinking-bg "#3a3a3a" :symbolic-border "#707070"
|
|
:system "#808080"
|
|
:input-prompt "#fab283" :input-fg "#e8e8e8"
|
|
:hint "#606060"
|
|
:status-bg "#141414" :status-fg "#e8e8e8"
|
|
:bg "#0a0a0a" :bg-panel "#141414" :bg-element "#1e1e1e"
|
|
:bg-input "#2e2e2e"
|
|
:text-muted "#808080"
|
|
:dot-connected "#7fd88f" :dot-disconnected "#e06c75"
|
|
:error "#e06c75"
|
|
:tool-running "#fab283" :tool-done "#7fd88f" :tool-error "#e06c75"
|
|
:separator "#3c3c3c" :accent "#fab283" :dim "#606060")
|
|
:gold
|
|
(:user-fg "#ffd700" :user-bg "#1e1e1e" :user-border "#ffd700"
|
|
:agent-header "#d4a574" :agent-fg "#e8e8e8"
|
|
:agent-border "#c0a080" :thinking-bg "#3a3a3a" :symbolic-border "#707070"
|
|
:system "#808080"
|
|
:input-prompt "#ffd700" :input-fg "#e8e8e8"
|
|
:hint "#606060"
|
|
:status-bg "#141414" :status-fg "#ffd700"
|
|
:bg "#0a0a0a" :bg-panel "#141414" :bg-element "#1e1e1e"
|
|
:bg-input "#2e2e2e"
|
|
:text-muted "#808080"
|
|
:dot-connected "#7fd88f" :dot-disconnected "#e06c75"
|
|
:error "#e06c75"
|
|
:tool-running "#ffd700" :tool-done "#7fd88f" :tool-error "#e06c75"
|
|
:separator "#3c3c3c" :accent "#ffd700" :dim "#606060")
|
|
:terracotta
|
|
(:user-fg "#e87a5d" :user-bg "#1e1e1e" :user-border "#e87a5d"
|
|
:agent-header "#d4956a" :agent-fg "#e0c8b0"
|
|
:agent-border "#c0a080" :thinking-bg "#3a3a3a" :symbolic-border "#707070"
|
|
:system "#808080"
|
|
:input-prompt "#e87a5d" :input-fg "#e0c8b0"
|
|
:hint "#606060"
|
|
:status-bg "#141414" :status-fg "#d4956a"
|
|
:bg "#0a0a0a" :bg-panel "#141414" :bg-element "#1e1e1e"
|
|
:bg-input "#2e2e2e"
|
|
:text-muted "#808080"
|
|
:dot-connected "#6cb85c" :dot-disconnected "#d94a3a"
|
|
:error "#d94a3a"
|
|
:tool-running "#e87a5d" :tool-done "#6cb85c" :tool-error "#d94a3a"
|
|
:separator "#3c3c3c" :accent "#e87a5d" :dim "#606060")
|
|
:sepia
|
|
(:user-fg "#c4a882" :user-bg "#1e1e1e" :user-border "#c4a882"
|
|
:agent-header "#b89870" :agent-fg "#d4c4a8"
|
|
:system "#808080"
|
|
:input-prompt "#c4a882" :input-fg "#d4c4a8"
|
|
:hint "#606060"
|
|
:status-bg "#141414" :status-fg "#b89870"
|
|
:bg "#0a0a0a" :bg-panel "#141414" :bg-element "#1e1e1e"
|
|
:bg-input "#2e2e2e"
|
|
:text-muted "#808080"
|
|
:dot-connected "#7aac5c" :dot-disconnected "#c84a3a"
|
|
:error "#c84a3a"
|
|
:tool-running "#c4a882" :tool-done "#7aac5c" :tool-error "#c84a3a"
|
|
:separator "#3c3c3c" :accent "#c4a882" :dim "#606060")
|
|
:nord-warm
|
|
(:user-fg "#d4a574" :user-bg "#1e1e1e" :user-border "#d4a574"
|
|
:agent-header "#c49870" :agent-fg "#e0d0c0"
|
|
:system "#808080"
|
|
:input-prompt "#d08770" :input-fg "#e0d0c0"
|
|
:hint "#606060"
|
|
:status-bg "#141414" :status-fg "#c8a080"
|
|
:bg "#0a0a0a" :bg-panel "#141414" :bg-element "#1e1e1e"
|
|
:bg-input "#2e2e2e"
|
|
:text-muted "#808080"
|
|
:dot-connected "#7cb860" :dot-disconnected "#d06050"
|
|
:error "#d06050"
|
|
:tool-running "#d08770" :tool-done "#7cb860" :tool-error "#d06050"
|
|
:separator "#3c3c3c" :accent "#d4a574" :dim "#606060")
|
|
:monokai-warm
|
|
(:user-fg "#e6b87d" :user-bg "#1e1e1e" :user-border "#e6b87d"
|
|
:agent-header "#d4a06a" :agent-fg "#d8c8b0"
|
|
:system "#808080"
|
|
:input-prompt "#e6b87d" :input-fg "#d8c8b0"
|
|
:hint "#606060"
|
|
:status-bg "#141414" :status-fg "#cc9966"
|
|
:bg "#0a0a0a" :bg-panel "#141414" :bg-element "#1e1e1e"
|
|
:bg-input "#2e2e2e"
|
|
:text-muted "#808080"
|
|
:dot-connected "#7ab85c" :dot-disconnected "#d94a3a"
|
|
:error "#d94a3a"
|
|
:tool-running "#e6b87d" :tool-done "#7ab85c" :tool-error "#d94a3a"
|
|
:separator "#3c3c3c" :accent "#e6b87d" :dim "#606060")
|
|
:gruvbox-warm
|
|
(:user-fg "#d8a657" :user-bg "#1e1e1e" :user-border "#d8a657"
|
|
:agent-header "#c8a070" :agent-fg "#e0c8a8"
|
|
:system "#808080"
|
|
:input-prompt "#d8a657" :input-fg "#e0c8a8"
|
|
:hint "#606060"
|
|
:status-bg "#141414" :status-fg "#c8a070"
|
|
:bg "#0a0a0a" :bg-panel "#141414" :bg-element "#1e1e1e"
|
|
:bg-input "#2e2e2e"
|
|
:text-muted "#808080"
|
|
:dot-connected "#7ab85c" :dot-disconnected "#d94a3a"
|
|
:error "#d94a3a"
|
|
:tool-running "#d8a657" :tool-done "#7ab85c" :tool-error "#d94a3a"
|
|
:separator "#3c3c3c" :accent "#d8a657" :dim "#606060")
|
|
:light-amber
|
|
(:user-fg "#cc6600" :user-bg "#f5f5f5" :user-border "#cc6600"
|
|
:agent-header "#8b6914" :agent-fg "#3a2a1a"
|
|
:agent-border "#a08060" :thinking-bg "#d4d4d4" :symbolic-border "#b0b0b0"
|
|
:system "#808080"
|
|
:input-prompt "#cc6600" :input-fg "#3a2a1a"
|
|
:hint "#a0a0a0"
|
|
:status-bg "#ebebeb" :status-fg "#3a2a1a"
|
|
:bg "#ffffff" :bg-panel "#f5f5f5" :bg-element "#ebebeb"
|
|
:bg-input "#d4d4d4"
|
|
:text-muted "#808080"
|
|
:dot-connected "#2e8b57" :dot-disconnected "#cc3300"
|
|
:error "#cc3300"
|
|
:tool-running "#cc6600" :tool-done "#2e8b57" :tool-error "#cc3300"
|
|
:separator "#d4d4d4" :accent "#cc6600" :dim "#a0a0a0")
|
|
:catppuccin
|
|
(:user-fg "#fab387" :user-bg "#1e1e2e" :user-border "#fab387"
|
|
:agent-header "#cba6f7" :agent-fg "#cdd6f4"
|
|
:agent-border "#a6adc8" :thinking-bg "#363650" :symbolic-border "#6c7086"
|
|
:system "#808080"
|
|
:input-prompt "#fab387" :input-fg "#cdd6f4"
|
|
:hint "#6c7086"
|
|
:status-bg "#181825" :status-fg "#a6adc8"
|
|
:bg "#11111b" :bg-panel "#181825" :bg-element "#1e1e2e"
|
|
:bg-input "#2e2e2e"
|
|
:text-muted "#6c7086"
|
|
:dot-connected "#a6e3a1" :dot-disconnected "#f38ba8"
|
|
:error "#f38ba8"
|
|
:tool-running "#fab387" :tool-done "#a6e3a1" :tool-error "#f38ba8"
|
|
:separator "#313244" :accent "#fab387" :dim "#585b70")
|
|
:tokyonight
|
|
(:user-fg "#ff9e64" :user-bg "#1a1b26" :user-border "#ff9e64"
|
|
:agent-header "#7aa2f7" :agent-fg "#a9b1d6"
|
|
:agent-border "#7982a8" :thinking-bg "#363b54" :symbolic-border "#565f89"
|
|
:system "#808080"
|
|
:input-prompt "#ff9e64" :input-fg "#a9b1d6"
|
|
:hint "#565f89"
|
|
:status-bg "#16161e" :status-fg "#9aa5ce"
|
|
:bg "#0f0f18" :bg-panel "#16161e" :bg-element "#1a1b26"
|
|
:bg-input "#2e2e2e"
|
|
:text-muted "#565f89"
|
|
:dot-connected "#9ece6a" :dot-disconnected "#db4b4b"
|
|
:error "#db4b4b"
|
|
:tool-running "#ff9e64" :tool-done "#9ece6a" :tool-error "#db4b4b"
|
|
:separator "#292e42" :accent "#ff9e64" :dim "#444b6a")
|
|
:dracula
|
|
(:user-fg "#ff9580" :user-bg "#1e1f2b" :user-border "#ff9580"
|
|
:agent-header "#bd93f9" :agent-fg "#f8f8f2"
|
|
:agent-border "#c0c0e0" :thinking-bg "#3a3b50" :symbolic-border "#6272a4"
|
|
:system "#808080"
|
|
:input-prompt "#ff9580" :input-fg "#f8f8f2"
|
|
:hint "#6272a4"
|
|
:status-bg "#191a24" :status-fg "#e0e0e0"
|
|
:bg "#0f101a" :bg-panel "#191a24" :bg-element "#1e1f2b"
|
|
:bg-input "#2e2e2e"
|
|
:text-muted "#6272a4"
|
|
:dot-connected "#50fa7b" :dot-disconnected "#ff5555"
|
|
:error "#ff5555"
|
|
:tool-running "#ff9580" :tool-done "#50fa7b" :tool-error "#ff5555"
|
|
:separator "#34354a" :accent "#ff9580" :dim "#5a5b7a")
|
|
:gemini
|
|
(:user-fg "#87afff" :user-bg "#1a1a1a" :user-border "#87afff"
|
|
:agent-header "#d7afff" :agent-fg "#ffffff"
|
|
:agent-border "#d0d0d0" :thinking-bg "#3a3a3a" :symbolic-border "#707070"
|
|
:system "#808080"
|
|
:input-prompt "#87afff" :input-fg "#ffffff"
|
|
:hint "#606060"
|
|
:status-bg "#141414" :status-fg "#afafaf"
|
|
:bg "#000000" :bg-panel "#141414" :bg-element "#1a1a1a"
|
|
:bg-input "#2e2e2e"
|
|
:text-muted "#808080"
|
|
:dot-connected "#d7ffd7" :dot-disconnected "#ff87af"
|
|
:error "#ff87af"
|
|
:tool-running "#87afff" :tool-done "#d7ffd7" :tool-error "#ff87af"
|
|
:separator "#3a3a3a" :accent "#87afff" :dim "#5f5f5f")
|
|
:mono
|
|
(:user-fg "#e0e0e0" :user-bg "#1a1a1a" :user-border "#808080"
|
|
:agent-header "#c0c0c0" :agent-fg "#d0d0d0"
|
|
:agent-border "#a0a0a0" :thinking-bg "#3a3a3a" :symbolic-border "#808080"
|
|
:system "#808080"
|
|
:input-prompt "#ffffff" :input-fg "#d0d0d0"
|
|
:hint "#606060"
|
|
:status-bg "#141414" :status-fg "#b0b0b0"
|
|
:bg "#0a0a0a" :bg-panel "#141414" :bg-element "#1a1a1a"
|
|
:bg-input "#2e2e2e"
|
|
:text-muted "#808080"
|
|
:dot-connected "#a0a0a0" :dot-disconnected "#808080"
|
|
:error "#808080"
|
|
:tool-running "#e0e0e0" :tool-done "#a0a0a0" :tool-error "#808080"
|
|
:separator "#303030" :accent "#ffffff" :dim "#505050"))
|
|
"13 theme presets (amber, gold, terracotta, sepia, nord-warm,
|
|
monokai-warm, gruvbox-warm, light-amber, catppuccin, tokyonight, dracula,
|
|
gemini, mono). Keys: :bg/:bg-panel/:bg-element/:bg-input/:text-muted.")
|
|
|
|
(defvar *tui-theme-current-name* :amber
|
|
"Name of the currently active theme preset.")
|
|
|
|
(defun theme-save ()
|
|
"Persist current theme to disk."
|
|
(let ((path (merge-pathnames ".cache/passepartout/theme.lisp"
|
|
(user-homedir-pathname))))
|
|
(uiop:ensure-all-directories-exist (list path))
|
|
(with-open-file (out path :direction :output :if-exists :supersede :if-does-not-exist :create)
|
|
(format out ";; Passepartout TUI theme — auto-generated~%")
|
|
(format out "(setf passepartout.channel-tui::*tui-theme* '~s)~%" *tui-theme*)
|
|
(format out "(setf passepartout.channel-tui::*tui-theme-current-name* ~s)~%" *tui-theme-current-name*))
|
|
t))
|
|
|
|
(defun theme-load ()
|
|
"Load persisted theme from disk. Called at startup.
|
|
Adds any missing keys with defaults to handle saved themes from older versions."
|
|
(let ((path (merge-pathnames ".cache/passepartout/theme.lisp"
|
|
(user-homedir-pathname))))
|
|
(when (uiop:file-exists-p path)
|
|
(ignore-errors (load path)))
|
|
;; Fill in any missing keys from the default preset
|
|
(let ((defaults (getf *tui-theme-presets* *tui-theme-current-name*)))
|
|
(when defaults
|
|
(dolist (key '(:bg-input :bg-element :text-muted :agent-border :thinking-bg :symbolic-border))
|
|
(unless (getf *tui-theme* key)
|
|
(let ((val (getf defaults key)))
|
|
(when val (setf (getf *tui-theme* key) val)))))))))
|
|
|
|
(defun theme-switch (name)
|
|
"Switch to a named theme preset. Returns the preset name or nil if not found."
|
|
(let* ((key (intern (string-upcase (string name)) :keyword))
|
|
(preset (getf *tui-theme-presets* key)))
|
|
(when preset
|
|
(setf *tui-theme* (copy-list preset)
|
|
*tui-theme-current-name* key)
|
|
(theme-save)
|
|
(setf (st :dirty) (list t t t))
|
|
key)))
|
|
|
|
(defun theme-color (role)
|
|
"Returns a hex color string for a semantic role, suitable for cl-tty."
|
|
(let ((val (or (getf *tui-theme* role) :white)))
|
|
(cond
|
|
((stringp val) val)
|
|
(t (case val
|
|
(:green "#00FF00") (:red "#FF0000") (:cyan "#00FFFF")
|
|
(:yellow "#FFFF00") (:magenta "#FF00FF") (:blue "#0000FF")
|
|
(:white "#FFFFFF") (:black "#000000")
|
|
(:bright-black "#666666") (:bright-yellow "#FFD700")
|
|
(t "#FFFFFF"))))))
|
|
|
|
(defun st (key) (getf *state* key))
|
|
(defun (setf st) (val key) (setf (getf *state* key) val))
|
|
|
|
(defun init-state ()
|
|
(setf *state*
|
|
(list :running t :mode :chat :connected nil :stream nil
|
|
:input-buffer nil :input-history nil :input-hpos 0
|
|
:messages (make-array 16 :adjustable t :fill-pointer 0)
|
|
:scroll-offset 0 :busy nil :cursor-pos 0
|
|
:cursor-line 0 :cursor-col 0
|
|
:pending-ctrl-x nil
|
|
:scroll-at-bottom t :scroll-notify nil
|
|
:streaming-text nil :url-buffer nil ; v0.7.1
|
|
:collapsed-gates nil ; v0.7.2
|
|
:search-mode nil :search-query "" ; v0.7.2
|
|
:search-matches nil :search-match-idx 0
|
|
:sidebar-mode :auto ; v0.8.0: :auto/:visible/:hidden
|
|
:sidebar-width 42 ; v0.8.0
|
|
:expand-tool-calls nil ; v0.8.0
|
|
:mcp-count 0 ; v0.8.0
|
|
:kill-ring nil ; v0.9.0
|
|
:dialog-stack nil ; v0.8.0
|
|
:minibuffer-active nil ; v0.8.0
|
|
:command-palette-active nil ; v0.8.0
|
|
:command-palette-dialog nil ; v0.8.0
|
|
:session-cost 0.0 ; v0.9.0
|
|
:daemon-version nil ; filled by handshake
|
|
:dirty (list nil nil nil))))
|
|
#+END_SRC
|
|
|
|
** Helpers
|
|
#+BEGIN_SRC lisp :tangle /home/user/.local/share/passepartout/lisp/channel-tui-state.lisp
|
|
(defun now ()
|
|
(multiple-value-bind (s m h) (get-decoded-time)
|
|
(declare (ignore s))
|
|
(format nil "~2,'0d:~2,'0d" h m)))
|
|
|
|
(defun input-string ()
|
|
(coerce (reverse (st :input-buffer)) 'string))
|
|
|
|
(defun input-insert-char (ch)
|
|
"Insert character at cursor position into the input buffer."
|
|
(let* ((buf (st :input-buffer))
|
|
(pos (or (st :cursor-pos) 0))
|
|
(s (coerce (reverse buf) 'string))
|
|
(new (concatenate 'string (subseq s 0 pos) (string ch) (subseq s pos))))
|
|
(setf (st :input-buffer) (reverse (coerce new 'list)))
|
|
(setf (st :cursor-pos) (1+ pos))))
|
|
|
|
(defun input-delete-char ()
|
|
"Delete character before cursor position (standard backspace)."
|
|
(let* ((buf (st :input-buffer))
|
|
(pos (or (st :cursor-pos) 0)))
|
|
(when (and buf (> pos 0))
|
|
(let* ((s (coerce (reverse buf) 'string))
|
|
(new (concatenate 'string (subseq s 0 (1- pos)) (subseq s pos))))
|
|
(setf (st :input-buffer) (reverse (coerce new 'list)))
|
|
(setf (st :cursor-pos) (1- pos))))))
|
|
|
|
(defun add-msg (role content &key gate-trace panel)
|
|
(vector-push-extend (list :role role :content content :time (now) :gate-trace gate-trace :panel panel) (st :messages))
|
|
;; v0.7.0: notify when scrolled up and new msg arrives
|
|
(unless (st :scroll-at-bottom)
|
|
(setf (st :scroll-notify) t))
|
|
(setf (st :dirty) (list t t nil)))
|
|
#+END_SRC
|
|
|
|
** Slash Commands
|
|
#+BEGIN_SRC lisp :tangle /home/user/.local/share/passepartout/lisp/channel-tui-state.lisp
|
|
(defvar *slash-commands*
|
|
'((:title "/eval <expr> — Evaluate Lisp" :value "/eval" :category :session)
|
|
(:title "/undo — Undo last operation" :value "/undo" :category :session)
|
|
(:title "/redo — Redo last operation" :value "/redo" :category :session)
|
|
(:title "/reconnect — Re-establish daemon" :value "/reconnect" :category :session)
|
|
(:title "/quit — Save history and exit" :value "/quit" :category :session)
|
|
(:title "/q — Quick quit" :value "/q" :category :session)
|
|
(:title "/why — Show last gate trace" :value "/why" :category :memory)
|
|
(:title "/identity — Edit IDENTITY.org" :value "/identity" :category :memory)
|
|
(:title "/tags — List tag severities" :value "/tags" :category :memory)
|
|
(:title "/audit <id> — Inspect memory" :value "/audit" :category :memory)
|
|
(:title "/audit verify — Memory integrity" :value "/audit verify" :category :memory)
|
|
(:title "/rewind <n> — Rewind to snapshot" :value "/rewind" :category :memory)
|
|
(:title "/sessions — Show memory snapshots" :value "/sessions" :category :memory)
|
|
(:title "/resume <n> — Resume from snapshot" :value "/resume" :category :memory)
|
|
(:title "/focus <project> — Set context" :value "/focus" :category :system)
|
|
(:title "/scope <scope> — Change scope" :value "/scope" :category :system)
|
|
(:title "/unfocus — Pop context" :value "/unfocus" :category :system)
|
|
(:title "/theme [name] — Show/switch theme" :value "/theme" :category :system)
|
|
(:title "/context — Show context summary" :value "/context" :category :system)
|
|
(:title "/context why <id> — Debug memory" :value "/context why" :category :system)
|
|
(:title "/context dropped — Estimate pruned" :value "/context dropped" :category :system)
|
|
(:title "/search <query> — Search messages" :value "/search" :category :navigation)
|
|
(:title "/help — Show commands" :value "/help" :category :help)
|
|
(:title "/help <topic> — Search manual" :value "/help <topic>" :category :help))
|
|
"Slash commands for minibuffer select-dialog.")
|
|
#+END_SRC
|
|
|
|
** Daemon Commands
|
|
#+BEGIN_SRC lisp :tangle /home/user/.local/share/passepartout/lisp/channel-tui-state.lisp
|
|
(defvar *daemon-commands*
|
|
'((:title "Status — Daemon health info" :value (:action :status) :category :session)
|
|
(:title "Stats — Daemon statistics" :value (:action :stats) :category :session)
|
|
(:title "Ping — Daemon reachability" :value (:action :ping) :category :session)
|
|
(:title "Memory Snapshot — Capture state" :value (:action :memory-snapshot) :category :memory)
|
|
(:title "Memory Rebuild — Rebuild indices" :value (:action :memory-rebuild) :category :memory)
|
|
(:title "Memory Compact — Optimize storage" :value (:action :memory-compact) :category :memory)
|
|
(:title "Reload Config — Reload configuration" :value (:action :reload-config) :category :system)
|
|
(:title "Reload Identity — Reload identity file" :value (:action :reload-identity) :category :system)
|
|
(:title "List Skills — Available skills" :value (:action :list-skills) :category :system)
|
|
(:title "Help — Show daemon help" :value (:action :help) :category :help))
|
|
"Daemon commands for the command palette (Ctrl+P).")
|
|
|
|
(defun all-commands ()
|
|
"Merge slash commands and daemon commands into one unified list."
|
|
(append *slash-commands* *daemon-commands*))
|
|
#+END_SRC
|
|
|
|
** Event Queue
|
|
#+BEGIN_SRC lisp :tangle /home/user/.local/share/passepartout/lisp/channel-tui-state.lisp
|
|
(defun queue-event (ev)
|
|
(bt:with-lock-held (*event-lock*) (push ev *event-queue*)))
|
|
|
|
(defun drain-queue ()
|
|
(bt:with-lock-held (*event-lock*)
|
|
(let ((evs (nreverse *event-queue*)))
|
|
(setf *event-queue* nil) evs)))
|
|
#+END_SRC
|