From a0694d6489de6d7192c834b81d9b72930fd82248 Mon Sep 17 00:00:00 2001 From: Amr Gharbeia Date: Wed, 20 May 2026 16:55:55 -0400 Subject: [PATCH] Move config/test/models to daemon TCP protocol, TUI uses .env fallback - Daemon: add handle-client-config inline handler for :config-get, :config-set, :config-list, :provider-test, :provider-models - TUI cmd-config: write .env directly, send reload to daemon if connected - TUI: /config test and /config models send TCP to daemon (fallback: daemon-not-running message) - Add Test Provider and Discover Models to Ctrl+P daemon commands --- org/channel-tui-main.org | 34 ++++++++++++++++++++++++++-------- org/channel-tui-state.org | 2 ++ org/core-transport.org | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 8 deletions(-) diff --git a/org/channel-tui-main.org b/org/channel-tui-main.org index 2408446..cc61d33 100644 --- a/org/channel-tui-main.org +++ b/org/channel-tui-main.org @@ -496,9 +496,15 @@ Called from handle-submit." (dolist (e (sort entries #'string-lessp :key #'car)) (format out "~a=~a~%" (car e) (cdr e))))))) +(defun daemon-send (action-plist) + "Send a message to the daemon if connected. Returns T if sent." + (let ((s (st :stream))) + (when (and s (open-stream-p s)) + (send-daemon (list :type :event :payload action-plist)) + t))) (defun cmd-config (text) - "Handle /config commands using direct .env file access." + "Handle /config commands. Writes .env directly, sends to daemon if connected." (let* ((parts (uiop:split-string text :separator '(#\Space))) (sub (and (>= (length parts) 2) (second parts)))) (case (and sub (intern (string-upcase sub) :keyword)) @@ -506,17 +512,29 @@ Called from handle-submit." (let ((name (third parts)) (key (fourth parts))) (if (and name key) - (progn (config-env-set (format nil "~a_API_KEY" (string-upcase name)) key) - (add-msg :system (format nil "✓ ~a API key set" name))) + (let ((env-key (format nil "~a_API_KEY" (string-upcase name)))) + (config-env-set env-key key) + (daemon-send (list :action :reload-config)) + (add-msg :system (format nil "✓ ~a API key set" name))) (add-msg :system "Usage: /config provider ")))) - ((:test :models :status) - (add-msg :system "Run this from the daemon session (Passepartout) — /config only reads/writes .env")) + (:test + (let ((name (third parts))) + (if (and name (daemon-send (list :action :provider-test :name name))) + (add-msg :system (format nil "Testing ~a... (response will appear)" name)) + (add-msg :system "* Daemon not running — start passepartout daemon *")))) + (:models + (let ((name (third parts))) + (if (and name (daemon-send (list :action :provider-models :name name))) + (add-msg :system (format nil "Discovering ~a models... (response will appear)" name)) + (add-msg :system "* Daemon not running — start passepartout daemon *")))) (:cascade (let ((slot (third parts)) (cascade (fourth parts))) (if cascade - (progn (config-env-set (if slot (format nil "~a_CASCADE" (string-upcase slot)) "PROVIDER_CASCADE") cascade) - (add-msg :system (format nil "✓ ~a cascade: ~a" (or slot "global") cascade))) + (let ((env-key (if slot (format nil "~a_CASCADE" (string-upcase slot)) "PROVIDER_CASCADE"))) + (config-env-set env-key cascade) + (daemon-send (list :action :reload-config)) + (add-msg :system (format nil "✓ ~a cascade: ~a" (or slot "global") cascade))) (add-msg :system (format nil "Cascade: ~a" (or (cdr (assoc "PROVIDER_CASCADE" (config-env-read) :test #'string-equal)) "not set")))))) (:proxy (let ((url (third parts))) @@ -537,7 +555,7 @@ Called from handle-submit." (progn (config-env-set key path) (add-msg :system (format nil "✓ ~a set" key))) (add-msg :system (format nil "~a: ~a" key (or (cdr (assoc key (config-env-read) :test #'string-equal)) "not set")))))) - (t (add-msg :system "Usage: /config provider | /config cascade | /config proxy | /config timeout | /config folder "))))) + (t (add-msg :system "Usage: /config provider | /config cascade | /config proxy | /config timeout | /config folder | /config test | /config models "))))) (defun cmd-identity (text) "Handle /identity: show or load identity from IDENTITY.org directly." diff --git a/org/channel-tui-state.org b/org/channel-tui-state.org index 44f4261..5436c0f 100644 --- a/org/channel-tui-state.org +++ b/org/channel-tui-state.org @@ -333,6 +333,8 @@ Semantic keys (all presets define these): '((:title "Status — Daemon health info" :value (:action :status)) (:title "Stats — Daemon statistics" :value (:action :stats)) (:title "Ping — Daemon reachability" :value (:action :ping)) + (:title "Test Provider — Check connection" :value (:action :provider-test)) + (:title "Discover Models — List available" :value (:action :provider-models)) (:title "Memory Snapshot — Capture state" :value (:action :memory-snapshot)) (:title "Memory Rebuild — Rebuild indices" :value (:action :memory-rebuild)) (:title "Memory Compact — Optimize storage" :value (:action :memory-compact)) diff --git a/org/core-transport.org b/org/core-transport.org index 2709699..eb6f3f2 100644 --- a/org/core-transport.org +++ b/org/core-transport.org @@ -171,10 +171,45 @@ The daemon sends a handshake message on connection, then enters a read loop, inj nil)))) (format stream "~a" (frame-message health-msg)) (finish-output stream))) + ((member (getf (getf msg :payload) :action) + '(:config-get :config-set :config-list + :provider-test :provider-models)) + (handle-client-config msg stream)) (t (stimulus-inject msg :stream stream)))))) (error (c) (log-message "CLIENT ERROR: ~a" c))) (ignore-errors (usocket:socket-close socket)))) +(defun handle-client-config (msg stream) + "Handle config/provider commands inline (not through the cognitive pipeline)." + (let* ((payload (getf msg :payload)) + (action (getf payload :action)) + (name (getf payload :name)) + (key (getf payload :key)) + (value (getf payload :value)) + (result nil)) + (case action + (:config-list + (setf result (with-output-to-string (out) + (dolist (e (sort (config-read) #'string-lessp :key #'car)) + (format out "~a=~a~%" (car e) (cdr e)))))) + (:config-get + (let ((val (config-get (intern (string-upcase key) :keyword)))) + (setf result (format nil "~a: ~:[not set~;~:*~a~]" key val)))) + (:config-set + (config-set (intern (string-upcase key) :keyword) value) + (setf result (format nil "✓ ~a set" key))) + (:provider-test + (let ((ok (ignore-errors (test-provider-connection + (intern (string-downcase name) :keyword))))) + (setf result (format nil "~a: ~:[✗ failed~;✓ connected~]" name ok)))) + (:provider-models + (let ((models (ignore-errors (test-provider-connection + (intern (string-downcase name) :keyword))))) + (setf result (format nil "~a models: ~a" name (or models "unavailable")))))) + (when result + (format stream "~a" (frame-message (list :type :event :payload (list :text result)))) + (finish-output stream)))) + (defun start-daemon (&key (port 9105) (max-retries 10)) "Starts the network listener for TUI/CLI clients. If PORT is taken, tries subsequent ports up to PORT+MAX-RETRIES."