feat(tui): add background reader, error handling, connection state
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 3s
This commit is contained in:
@@ -75,33 +75,73 @@
|
|||||||
(when (string= cmd "/exit") (setf *is-running* nil))
|
(when (string= cmd "/exit") (setf *is-running* nil))
|
||||||
(when (string= cmd "/clear") (setf *chat-history* nil))))
|
(when (string= cmd "/clear") (setf *chat-history* nil))))
|
||||||
|
|
||||||
|
(defun start-background-reader (stream)
|
||||||
|
"Starts a thread that reads framed messages from the daemon stream."
|
||||||
|
(bt:make-thread
|
||||||
|
(lambda ()
|
||||||
|
(loop while *is-running* do
|
||||||
|
(handler-case
|
||||||
|
(let* ((len-buf (make-string 6))
|
||||||
|
(count (read-sequence len-buf stream)))
|
||||||
|
(when (= count 6)
|
||||||
|
(let* ((msg-len (parse-integer len-buf :radix 16))
|
||||||
|
(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)
|
||||||
|
(enqueue-msg "* Connected to daemon *"))
|
||||||
|
((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)
|
||||||
|
(when *is-running*
|
||||||
|
(enqueue-msg (format nil "ERROR: Connection lost (~a)" c))
|
||||||
|
(setf *is-running* nil))))))
|
||||||
|
:name "opencortex-tui-reader"))
|
||||||
|
|
||||||
(defun main ()
|
(defun main ()
|
||||||
(handler-case
|
(handler-case
|
||||||
(setf *socket* (usocket:socket-connect *daemon-host* *daemon-port*))
|
(setf *socket* (usocket:socket-connect *daemon-host* *daemon-port*))
|
||||||
(error (e) (format t "Offline: ~a~%" e) (return-from main)))
|
(error (e) (format t "Offline: ~a~%" e) (return-from main)))
|
||||||
(setf *stream* (usocket:socket-stream *socket*))
|
(setf *stream* (usocket:socket-stream *socket*))
|
||||||
|
|
||||||
|
;; 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
|
(unwind-protect
|
||||||
(with-screen (scr :input-echoing nil :input-blocking nil :enable-colors t)
|
(handler-case
|
||||||
(let* ((h (height scr)) (w (width scr)))
|
(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))
|
(let* ((h (height scr)) (w (width scr)))
|
||||||
(input-win (make-instance 'window :height 1 :width (- w 2) :position (list (- h 2) 1) :border t)))
|
(let ((chat-win (make-instance 'window :height (- h 5) :width (- w 2) :position '(1 1) :border t))
|
||||||
(setf (input-blocking input-win) nil)
|
(input-win (make-instance 'window :height 1 :width (- w 2) :position (list (- h 2) 1) :border t)))
|
||||||
(loop :while *is-running* :do
|
(setf (input-blocking input-win) nil)
|
||||||
(let ((msgs (dequeue-msgs)))
|
(start-background-reader *stream*)
|
||||||
(when msgs
|
(loop :while *is-running* :do
|
||||||
(dolist (m msgs) (push m *chat-history*))
|
(let ((msgs (dequeue-msgs)))
|
||||||
(render-chat chat-win)))
|
(when msgs
|
||||||
(let* ((ev (get-event input-win))
|
(dolist (m msgs) (push m *chat-history*))
|
||||||
(ch (when (and ev (typep ev 'event)) (event-key ev))))
|
(render-chat chat-win)))
|
||||||
(when ch
|
(let* ((ev (get-event input-win))
|
||||||
(cond
|
(ch (when (and ev (typep ev 'event)) (event-key ev))))
|
||||||
((or (eq ch #\Newline) (eq ch #\Return)) (handle-return *stream*))
|
(when ch
|
||||||
((or (eq ch :backspace) (eq ch (code-char 127))) (handle-backspace))
|
(cond
|
||||||
((characterp ch) (vector-push-extend ch *input-buffer*))))
|
((or (eq ch #\Newline) (eq ch #\Return)) (handle-return *stream*))
|
||||||
(clear input-win)
|
((or (eq ch :backspace) (eq ch (code-char 127))) (handle-backspace))
|
||||||
(add-string input-win (format nil "▶ ~a" (coerce *input-buffer* 'string)) :y 0 :x 1)
|
((characterp ch) (vector-push-extend ch *input-buffer*))))
|
||||||
(refresh input-win))
|
(clear input-win)
|
||||||
(sleep 0.02)))))
|
(add-string input-win (format nil "▶ ~a" (coerce *input-buffer* 'string)) :y 0 :x 1)
|
||||||
|
(refresh input-win))
|
||||||
|
(sleep 0.02)))))
|
||||||
|
(error (c)
|
||||||
|
(format t "TUI Error: ~a~%" c)))
|
||||||
(setf *is-running* nil)
|
(setf *is-running* nil)
|
||||||
(when *socket* (ignore-errors (usocket:socket-close *socket*)))))
|
(when *socket* (ignore-errors (usocket:socket-close *socket*)))))
|
||||||
|
|||||||
@@ -126,6 +126,39 @@ The OpenCortex TUI Client is a standalone Common Lisp application built on **Cro
|
|||||||
(when (string= cmd "/clear") (setf *chat-history* nil))))
|
(when (string= cmd "/clear") (setf *chat-history* nil))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
|
** Background Reader
|
||||||
|
#+begin_src lisp
|
||||||
|
(defun start-background-reader (stream)
|
||||||
|
"Starts a thread that reads framed messages from the daemon stream."
|
||||||
|
(bt:make-thread
|
||||||
|
(lambda ()
|
||||||
|
(loop while *is-running* do
|
||||||
|
(handler-case
|
||||||
|
(let* ((len-buf (make-string 6))
|
||||||
|
(count (read-sequence len-buf stream)))
|
||||||
|
(when (= count 6)
|
||||||
|
(let* ((msg-len (parse-integer len-buf :radix 16))
|
||||||
|
(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)
|
||||||
|
(enqueue-msg "* Connected to daemon *"))
|
||||||
|
((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)
|
||||||
|
(when *is-running*
|
||||||
|
(enqueue-msg (format nil "ERROR: Connection lost (~a)" c))
|
||||||
|
(setf *is-running* nil))))))
|
||||||
|
:name "opencortex-tui-reader"))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
** Main Entry Point
|
** Main Entry Point
|
||||||
#+begin_src lisp
|
#+begin_src lisp
|
||||||
(defun main ()
|
(defun main ()
|
||||||
@@ -134,28 +167,38 @@ The OpenCortex TUI Client is a standalone Common Lisp application built on **Cro
|
|||||||
(error (e) (format t "Offline: ~a~%" e) (return-from main)))
|
(error (e) (format t "Offline: ~a~%" e) (return-from main)))
|
||||||
(setf *stream* (usocket:socket-stream *socket*))
|
(setf *stream* (usocket:socket-stream *socket*))
|
||||||
|
|
||||||
|
;; 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
|
(unwind-protect
|
||||||
(with-screen (scr :input-echoing nil :input-blocking nil :enable-colors t)
|
(handler-case
|
||||||
(let* ((h (height scr)) (w (width scr)))
|
(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))
|
(let* ((h (height scr)) (w (width scr)))
|
||||||
(input-win (make-instance 'window :height 1 :width (- w 2) :position (list (- h 2) 1) :border t)))
|
(let ((chat-win (make-instance 'window :height (- h 5) :width (- w 2) :position '(1 1) :border t))
|
||||||
(setf (input-blocking input-win) nil)
|
(input-win (make-instance 'window :height 1 :width (- w 2) :position (list (- h 2) 1) :border t)))
|
||||||
(loop :while *is-running* :do
|
(setf (input-blocking input-win) nil)
|
||||||
(let ((msgs (dequeue-msgs)))
|
(start-background-reader *stream*)
|
||||||
(when msgs
|
(loop :while *is-running* :do
|
||||||
(dolist (m msgs) (push m *chat-history*))
|
(let ((msgs (dequeue-msgs)))
|
||||||
(render-chat chat-win)))
|
(when msgs
|
||||||
(let* ((ev (get-event input-win))
|
(dolist (m msgs) (push m *chat-history*))
|
||||||
(ch (when (and ev (typep ev 'event)) (event-key ev))))
|
(render-chat chat-win)))
|
||||||
(when ch
|
(let* ((ev (get-event input-win))
|
||||||
(cond
|
(ch (when (and ev (typep ev 'event)) (event-key ev))))
|
||||||
((or (eq ch #\Newline) (eq ch #\Return)) (handle-return *stream*))
|
(when ch
|
||||||
((or (eq ch :backspace) (eq ch (code-char 127))) (handle-backspace))
|
(cond
|
||||||
((characterp ch) (vector-push-extend ch *input-buffer*))))
|
((or (eq ch #\Newline) (eq ch #\Return)) (handle-return *stream*))
|
||||||
(clear input-win)
|
((or (eq ch :backspace) (eq ch (code-char 127))) (handle-backspace))
|
||||||
(add-string input-win (format nil "▶ ~a" (coerce *input-buffer* 'string)) :y 0 :x 1)
|
((characterp ch) (vector-push-extend ch *input-buffer*))))
|
||||||
(refresh input-win))
|
(clear input-win)
|
||||||
(sleep 0.02)))))
|
(add-string input-win (format nil "▶ ~a" (coerce *input-buffer* 'string)) :y 0 :x 1)
|
||||||
|
(refresh input-win))
|
||||||
|
(sleep 0.02)))))
|
||||||
|
(error (c)
|
||||||
|
(format t "TUI Error: ~a~%" c)))
|
||||||
(setf *is-running* nil)
|
(setf *is-running* nil)
|
||||||
(when *socket* (ignore-errors (usocket:socket-close *socket*)))))
|
(when *socket* (ignore-errors (usocket:socket-close *socket*)))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|||||||
Reference in New Issue
Block a user