(defpackage :opencortex-tui-tests (:use :cl :opencortex) (:export #:tui-suite)) (in-package :opencortex-tui-tests) (eval-when (:compile-toplevel :load-toplevel :execute) (ql:quickload :fiveam :silent t)) (fiveam:def-suite tui-suite :description "Verification of the TUI parsing and styling logic") (fiveam:in-suite tui-suite) (fiveam:test test-tui-connection-drop "Tier 2 Chaos: Verify that handle-return degrades gracefully when the daemon connection is lost." ;; Create a closed stream to simulate connection drop (mock-stream (make-string-output-stream))) (close mock-stream) (opencortex.tui::handle-return mock-stream) ;; Check if the error was enqueued to history instead of crashing (in-package :cl-user) (defpackage :opencortex.tui (:use :cl :croatoan :usocket :bordeaux-threads) (:export :main)) (in-package :opencortex.tui) (defun enqueue-msg (msg) "Thread-safe addition to incoming message queue." (defun dequeue-msgs () "Thread-safe retrieval of incoming messages." msgs))) (defun get-line-style (text) (cond ((uiop:string-prefix-p "⬆" text) '(:cyan)) ((uiop:string-prefix-p "🤔" text) '(:italic)) ((uiop:string-prefix-p "ERROR" text) '(:bold :red)) (t nil))) (defun render-chat (win) (clear win) (view-height (max 0 (- h 2))) (end-idx (min history-len (+ start-idx view-height))) (loop for msg in slice for i from 1 do (add-string win (format nil "│ ~a" msg) :y i :x 1 :attributes (get-line-style msg))) (refresh win))) (defun handle-backspace () (defun handle-return (stream) (when (> (length cmd) 0) (enqueue-msg (format nil "⬆ ~a" cmd)) (handler-case (progn (when (and stream (open-stream-p stream)) :META (list :SOURCE :tui) :PAYLOAD (list :SENSOR :user-input :TEXT cmd))) (payload (format nil "~s" msg)) (len (length payload))) (format stream "~6,'0x~a" len payload) (finish-output stream))) (enqueue-msg "✓ Sent")) (error (c) (format t "Send error: ~a~%" c) (enqueue-msg "ERROR: Connection to daemon lost.") (defun start-background-reader (stream) "Starts a thread that reads framed messages from the daemon stream." (bt:make-thread (lambda () (handler-case (count (read-sequence len-buf stream))) (when (= count 6) (msg-buf (make-string msg-len))) (read-sequence msg-buf stream) (let ((msg (read-from-string msg-buf))) (let ((payload (getf msg :payload))) (cond ((eq (getf payload :action) :handshake) ((and (eq (getf payload :sensor) :loop-error) (not (string= (or (getf payload :message) "") "Neural Cascade Failure: All providers exhausted."))) (enqueue-msg (format nil "ERROR: Daemon loop error (~a)" (getf payload :message)))) (t (let ((text (or (getf payload :text) (format nil "~a" payload)))) (enqueue-msg (format nil "⬇ ~a" text))))))))) (error (c) (enqueue-msg (format nil "ERROR: Connection lost (~a)" c)) :name "opencortex-tui-reader"))) (defun main () (handler-case (error (e) (format t "Offline: ~a~%" e) (return-from main))) ;; Guard: Croatoan needs a real terminal (TERM env var, real TTY) (unless (uiop:getenv "TERM") (format t "TUI requires a terminal. Set TERM environment variable.~%") (format t "Or use: echo 'your message' | nc localhost 9105~%") (return-from main)) (unwind-protect (handler-case (with-screen (scr :input-echoing nil :input-blocking nil :enable-colors t) (let ((chat-win (make-instance 'window :height (- h 5) :width (- w 2) :position '(1 1) :border t)) (input-win (make-instance 'window :height 1 :width (- w 2) :position (list (- h 2) 1) :border t))) (setf (input-blocking input-win) nil) (let ((msgs (dequeue-msgs))) (when msgs (render-chat chat-win))) (ch (when (and ev (typep ev 'event)) (event-key ev)))) (when ch (cond ((or (eq ch :backspace) (eq ch (code-char 127))) (handle-backspace)) (clear input-win) (refresh input-win)) (sleep 0.02))))) (error (c) (format t "TUI Error: ~a~%" c)))