refactor(harness): centralize mandates, fix TUI reader structure, and enhance memory/perceive
This commit is contained in:
18
GEMINI.md
18
GEMINI.md
@@ -1,18 +0,0 @@
|
|||||||
# OpenCortex Agent Mandates
|
|
||||||
|
|
||||||
This file defines mandatory workflows and technical standards for the Gemini CLI agent operating within the OpenCortex environment. These mandates supersede general defaults.
|
|
||||||
|
|
||||||
## Lisp Integrity Mandates
|
|
||||||
- **Validation:** Before applying any change to a `.lisp` file or a Lisp block in an `.org` file, you MUST use `utils-lisp-validate` to ensure structural and semantic integrity.
|
|
||||||
- **Formatting:** All generated Lisp code MUST be piped through `utils-lisp-format` to maintain project-standard indentation before being saved.
|
|
||||||
- **Structural Editing:** When modifying complex Lisp forms (nested macros or large functions), prefer using `utils-lisp-structural-extract` and `utils-lisp-structural-wrap` to avoid manual parenthesis errors.
|
|
||||||
- **Verification:** For new or non-trivial logic, use `utils-lisp-eval` to test the behavior of the isolated S-expression in a live REPL environment before tangling.
|
|
||||||
|
|
||||||
## Literate Org Mandates
|
|
||||||
- **AST Integrity:** When modifying Org files, utilize `utils-org-set-property`, `utils-org-set-todo`, and `utils-org-add-headline` to manipulate the document structure programmatically whenever possible.
|
|
||||||
- **ID Management:** Every new headline intended for tracking or tangling MUST have a unique ID generated via `utils-org-generate-id`.
|
|
||||||
|
|
||||||
## Engineering Workflow
|
|
||||||
- **Commit-Before-Modify:** Verify the git state is clean before starting a multi-file refactor.
|
|
||||||
- **Tangle Sync:** After modifying any `.org` file, you MUST ensure the corresponding `.lisp` artifacts are tangled and in sync.
|
|
||||||
- **Validation:** Run the project-specific test suite (`sbcl --load opencortex.asd`) after every significant change to verify system stability.
|
|
||||||
@@ -84,8 +84,8 @@
|
|||||||
(format t "==================================================~%")
|
(format t "==================================================~%")
|
||||||
(handler-case
|
(handler-case
|
||||||
(progn
|
(progn
|
||||||
(when (fboundp 'doctor-run-all)
|
(when (fboundp 'doctor-run-all)
|
||||||
(let ((result (doctor-run-all :auto-install nil)))
|
(let ((result (doctor-run-all)))
|
||||||
(setf *health-check-ran* t)
|
(setf *health-check-ran* t)
|
||||||
(if result
|
(if result
|
||||||
(progn
|
(progn
|
||||||
|
|||||||
@@ -4,13 +4,16 @@
|
|||||||
(defvar *history-store* (make-hash-table :test 'equal)
|
(defvar *history-store* (make-hash-table :test 'equal)
|
||||||
"Immutable Merkle-Tree versioning store mapping hashes to objects.")
|
"Immutable Merkle-Tree versioning store mapping hashes to objects.")
|
||||||
|
|
||||||
|
(defun lookup-object (id)
|
||||||
|
(gethash id *memory*))
|
||||||
|
|
||||||
(defstruct org-object
|
(defstruct org-object
|
||||||
id type attributes content vector parent-id children version last-sync hash)
|
id type attributes content vector parent-id children version last-sync hash)
|
||||||
|
|
||||||
(defmethod make-load-form ((obj org-object) &optional env)
|
(defmethod make-load-form ((obj org-object) &optional env)
|
||||||
(make-load-form-saving-slots obj :environment env))
|
(make-load-form-saving-slots obj :environment env))
|
||||||
|
|
||||||
(defun copy-org-object (obj)
|
(defun deep-copy-org-object (obj)
|
||||||
(make-org-object :id (org-object-id obj)
|
(make-org-object :id (org-object-id obj)
|
||||||
:type (org-object-type obj)
|
:type (org-object-type obj)
|
||||||
:attributes (copy-list (org-object-attributes obj))
|
:attributes (copy-list (org-object-attributes obj))
|
||||||
@@ -71,7 +74,7 @@
|
|||||||
|
|
||||||
(defun snapshot-memory ()
|
(defun snapshot-memory ()
|
||||||
(let ((snapshot (make-hash-table :test 'equal :size (hash-table-size *memory*))))
|
(let ((snapshot (make-hash-table :test 'equal :size (hash-table-size *memory*))))
|
||||||
(maphash (lambda (k v) (setf (gethash k snapshot) (copy-org-object v))) *memory*)
|
(maphash (lambda (k v) (setf (gethash k snapshot) (deep-copy-org-object v))) *memory*)
|
||||||
(push (list :timestamp (get-universal-time) :data snapshot) *object-store-snapshots*)
|
(push (list :timestamp (get-universal-time) :data snapshot) *object-store-snapshots*)
|
||||||
(when (> (length *object-store-snapshots*) 20) (setf *object-store-snapshots* (subseq *object-store-snapshots* 0 20)))
|
(when (> (length *object-store-snapshots*) 20) (setf *object-store-snapshots* (subseq *object-store-snapshots* 0 20)))
|
||||||
(harness-log "MEMORY - CoW Memory snapshot created.")))
|
(harness-log "MEMORY - CoW Memory snapshot created.")))
|
||||||
|
|||||||
@@ -21,6 +21,12 @@ The Memory module is the cognitive bedrock of the opencortex. It is not a databa
|
|||||||
"Immutable Merkle-Tree versioning store mapping hashes to objects.")
|
"Immutable Merkle-Tree versioning store mapping hashes to objects.")
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
|
** Object Lookup
|
||||||
|
#+begin_src lisp
|
||||||
|
(defun lookup-object (id)
|
||||||
|
(gethash id *memory*))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
** The Data Structure (org-object)
|
** The Data Structure (org-object)
|
||||||
#+begin_src lisp
|
#+begin_src lisp
|
||||||
(defstruct org-object
|
(defstruct org-object
|
||||||
@@ -29,7 +35,7 @@ The Memory module is the cognitive bedrock of the opencortex. It is not a databa
|
|||||||
(defmethod make-load-form ((obj org-object) &optional env)
|
(defmethod make-load-form ((obj org-object) &optional env)
|
||||||
(make-load-form-saving-slots obj :environment env))
|
(make-load-form-saving-slots obj :environment env))
|
||||||
|
|
||||||
(defun copy-org-object (obj)
|
(defun deep-copy-org-object (obj)
|
||||||
(make-org-object :id (org-object-id obj)
|
(make-org-object :id (org-object-id obj)
|
||||||
:type (org-object-type obj)
|
:type (org-object-type obj)
|
||||||
:attributes (copy-list (org-object-attributes obj))
|
:attributes (copy-list (org-object-attributes obj))
|
||||||
@@ -99,7 +105,7 @@ The Memory module is the cognitive bedrock of the opencortex. It is not a databa
|
|||||||
|
|
||||||
(defun snapshot-memory ()
|
(defun snapshot-memory ()
|
||||||
(let ((snapshot (make-hash-table :test 'equal :size (hash-table-size *memory*))))
|
(let ((snapshot (make-hash-table :test 'equal :size (hash-table-size *memory*))))
|
||||||
(maphash (lambda (k v) (setf (gethash k snapshot) (copy-org-object v))) *memory*)
|
(maphash (lambda (k v) (setf (gethash k snapshot) (deep-copy-org-object v))) *memory*)
|
||||||
(push (list :timestamp (get-universal-time) :data snapshot) *object-store-snapshots*)
|
(push (list :timestamp (get-universal-time) :data snapshot) *object-store-snapshots*)
|
||||||
(when (> (length *object-store-snapshots*) 20) (setf *object-store-snapshots* (subseq *object-store-snapshots* 0 20)))
|
(when (> (length *object-store-snapshots*) 20) (setf *object-store-snapshots* (subseq *object-store-snapshots* 0 20)))
|
||||||
(harness-log "MEMORY - CoW Memory snapshot created.")))
|
(harness-log "MEMORY - CoW Memory snapshot created.")))
|
||||||
|
|||||||
@@ -254,6 +254,7 @@
|
|||||||
;; --- Debugger Hook ---
|
;; --- Debugger Hook ---
|
||||||
(setf *debugger-hook* (lambda (condition hook)
|
(setf *debugger-hook* (lambda (condition hook)
|
||||||
"Friendly error handler - shows diagnostic message instead of raw debugger."
|
"Friendly error handler - shows diagnostic message instead of raw debugger."
|
||||||
|
(declare (ignore hook))
|
||||||
(format t "~%")
|
(format t "~%")
|
||||||
(format t "┌─────────────────────────────────────────────┐~%")
|
(format t "┌─────────────────────────────────────────────┐~%")
|
||||||
(format t "│ ERROR: ~A~%" (type-of condition))
|
(format t "│ ERROR: ~A~%" (type-of condition))
|
||||||
|
|||||||
@@ -268,8 +268,10 @@ The ~package.lisp~ file defines the public API of the ~opencortex~ harness.
|
|||||||
(finish-output)))
|
(finish-output)))
|
||||||
|
|
||||||
;; --- Debugger Hook ---
|
;; --- Debugger Hook ---
|
||||||
|
#+begin_src lisp :tangle package.lisp
|
||||||
(setf *debugger-hook* (lambda (condition hook)
|
(setf *debugger-hook* (lambda (condition hook)
|
||||||
"Friendly error handler - shows diagnostic message instead of raw debugger."
|
"Friendly error handler - shows diagnostic message instead of raw debugger."
|
||||||
|
(declare (ignore hook))
|
||||||
(format t "~%")
|
(format t "~%")
|
||||||
(format t "┌─────────────────────────────────────────────┐~%")
|
(format t "┌─────────────────────────────────────────────┐~%")
|
||||||
(format t "│ ERROR: ~A~%" (type-of condition))
|
(format t "│ ERROR: ~A~%" (type-of condition))
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
(in-package :opencortex)
|
(in-package :opencortex)
|
||||||
|
|
||||||
|
(defvar *interrupt-flag* nil)
|
||||||
(defvar *async-sensors* '(:chat-message :delegation :user-command)
|
(defvar *async-sensors* '(:chat-message :delegation :user-command)
|
||||||
"Sensors that are processed in dedicated threads.")
|
"Sensors that are processed in dedicated threads.")
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ The Perceive stage is the "sensory cortex" of OpenCortex. Its job is to take raw
|
|||||||
(in-package :opencortex)
|
(in-package :opencortex)
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
|
** Interrupt Handling
|
||||||
|
#+begin_src lisp
|
||||||
|
(defvar *interrupt-flag* nil)
|
||||||
|
#+end_src
|
||||||
|
|
||||||
** Sensor Configuration
|
** Sensor Configuration
|
||||||
#+begin_src lisp
|
#+begin_src lisp
|
||||||
(defvar *async-sensors* '(:chat-message :delegation :user-command)
|
(defvar *async-sensors* '(:chat-message :delegation :user-command)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
(in-package :cl-user)
|
(in-package :cl-user)
|
||||||
(defpackage :opencortex.tui
|
(defpackage :opencortex.tui
|
||||||
(:use :cl :croatoan :usocket)
|
(:use :cl :croatoan :usocket :bordeaux-threads)
|
||||||
(:export :main))
|
(:export :main))
|
||||||
(in-package :opencortex.tui)
|
(in-package :opencortex.tui)
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
(defvar *stream* nil)
|
(defvar *stream* nil)
|
||||||
(defvar *chat-history* nil)
|
(defvar *chat-history* nil)
|
||||||
(defvar *scroll-index* 0)
|
(defvar *scroll-index* 0)
|
||||||
(defvar *input-buffer* (make-array 0 :element-type 'char :fill-pointer 0 :adjustable t))
|
(defvar *input-buffer* (make-array 0 :element-type 'character :fill-pointer 0 :adjustable t))
|
||||||
(defvar *is-running* t)
|
(defvar *is-running* t)
|
||||||
(defvar *queue-lock* (bt:make-lock))
|
(defvar *queue-lock* (bt:make-lock))
|
||||||
(defvar *incoming-msgs* nil)
|
(defvar *incoming-msgs* nil)
|
||||||
@@ -70,10 +70,10 @@
|
|||||||
(enqueue-msg "✓ Sent"))
|
(enqueue-msg "✓ Sent"))
|
||||||
(error (c)
|
(error (c)
|
||||||
(format t "Send error: ~a~%" c)
|
(format t "Send error: ~a~%" c)
|
||||||
(enqueue-msg "ERROR: Connection to daemon lost.")
|
(enqueue-msg "ERROR: Connection to daemon lost.")
|
||||||
(setf *is-running* nil))))
|
(setf *is-running* nil))))
|
||||||
(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)
|
(defun start-background-reader (stream)
|
||||||
"Starts a thread that reads framed messages from the daemon stream."
|
"Starts a thread that reads framed messages from the daemon stream."
|
||||||
@@ -98,12 +98,13 @@
|
|||||||
(getf payload :message))))
|
(getf payload :message))))
|
||||||
(t
|
(t
|
||||||
(let ((text (or (getf payload :text) (format nil "~a" payload))))
|
(let ((text (or (getf payload :text) (format nil "~a" payload))))
|
||||||
(enqueue-msg (format nil "⬇ ~a" text)))))))))
|
(enqueue-msg (format nil "⬇ ~a" text))))))))))
|
||||||
(error (c)
|
(error (c)
|
||||||
(when *is-running*
|
(when *is-running*
|
||||||
(enqueue-msg (format nil "ERROR: Connection lost (~a)" c))
|
(enqueue-msg (format nil "ERROR: Connection lost (~a)" c))
|
||||||
(setf *is-running* nil))))))
|
(setf *is-running* nil))))))
|
||||||
:name "opencortex-tui-reader"))
|
:name "opencortex-tui-reader")))
|
||||||
|
)
|
||||||
|
|
||||||
(defun main ()
|
(defun main ()
|
||||||
(handler-case
|
(handler-case
|
||||||
@@ -144,4 +145,4 @@
|
|||||||
(error (c)
|
(error (c)
|
||||||
(format t "TUI Error: ~a~%" 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*))))
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ The OpenCortex TUI Client is a standalone Common Lisp application built on **Cro
|
|||||||
(fiveam:test test-tui-connection-drop
|
(fiveam:test test-tui-connection-drop
|
||||||
"Tier 2 Chaos: Verify that handle-return degrades gracefully when the daemon connection is lost."
|
"Tier 2 Chaos: Verify that handle-return degrades gracefully when the daemon connection is lost."
|
||||||
(let ((opencortex.tui::*incoming-msgs* nil)
|
(let ((opencortex.tui::*incoming-msgs* nil)
|
||||||
(opencortex.tui::*input-buffer* (make-array 5 :element-type 'char :initial-contents "hello" :fill-pointer 5 :adjustable t))
|
(opencortex.tui::*input-buffer* (make-array 5 :element-type 'character :initial-contents "hello" :fill-pointer 5 :adjustable t))
|
||||||
;; Create a closed stream to simulate connection drop
|
;; Create a closed stream to simulate connection drop
|
||||||
(mock-stream (make-string-output-stream)))
|
(mock-stream (make-string-output-stream)))
|
||||||
(close mock-stream)
|
(close mock-stream)
|
||||||
@@ -38,7 +38,7 @@ The OpenCortex TUI Client is a standalone Common Lisp application built on **Cro
|
|||||||
#+begin_src lisp
|
#+begin_src lisp
|
||||||
(in-package :cl-user)
|
(in-package :cl-user)
|
||||||
(defpackage :opencortex.tui
|
(defpackage :opencortex.tui
|
||||||
(:use :cl :croatoan :usocket)
|
(:use :cl :croatoan :usocket :bordeaux-threads)
|
||||||
(:export :main))
|
(:export :main))
|
||||||
(in-package :opencortex.tui)
|
(in-package :opencortex.tui)
|
||||||
#+end_src
|
#+end_src
|
||||||
@@ -51,7 +51,7 @@ The OpenCortex TUI Client is a standalone Common Lisp application built on **Cro
|
|||||||
(defvar *stream* nil)
|
(defvar *stream* nil)
|
||||||
(defvar *chat-history* nil)
|
(defvar *chat-history* nil)
|
||||||
(defvar *scroll-index* 0)
|
(defvar *scroll-index* 0)
|
||||||
(defvar *input-buffer* (make-array 0 :element-type 'char :fill-pointer 0 :adjustable t))
|
(defvar *input-buffer* (make-array 0 :element-type 'character :fill-pointer 0 :adjustable t))
|
||||||
(defvar *is-running* t)
|
(defvar *is-running* t)
|
||||||
(defvar *queue-lock* (bt:make-lock))
|
(defvar *queue-lock* (bt:make-lock))
|
||||||
(defvar *incoming-msgs* nil)
|
(defvar *incoming-msgs* nil)
|
||||||
@@ -120,10 +120,10 @@ The OpenCortex TUI Client is a standalone Common Lisp application built on **Cro
|
|||||||
(enqueue-msg "✓ Sent"))
|
(enqueue-msg "✓ Sent"))
|
||||||
(error (c)
|
(error (c)
|
||||||
(format t "Send error: ~a~%" c)
|
(format t "Send error: ~a~%" c)
|
||||||
(enqueue-msg "ERROR: Connection to daemon lost.")
|
(enqueue-msg "ERROR: Connection to daemon lost.")
|
||||||
(setf *is-running* nil))))
|
(setf *is-running* nil))))
|
||||||
(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))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
** Background Reader
|
** Background Reader
|
||||||
@@ -151,12 +151,13 @@ The OpenCortex TUI Client is a standalone Common Lisp application built on **Cro
|
|||||||
(getf payload :message))))
|
(getf payload :message))))
|
||||||
(t
|
(t
|
||||||
(let ((text (or (getf payload :text) (format nil "~a" payload))))
|
(let ((text (or (getf payload :text) (format nil "~a" payload))))
|
||||||
(enqueue-msg (format nil "⬇ ~a" text)))))))))
|
(enqueue-msg (format nil "⬇ ~a" text))))))))))
|
||||||
(error (c)
|
(error (c)
|
||||||
(when *is-running*
|
(when *is-running*
|
||||||
(enqueue-msg (format nil "ERROR: Connection lost (~a)" c))
|
(enqueue-msg (format nil "ERROR: Connection lost (~a)" c))
|
||||||
(setf *is-running* nil))))))
|
(setf *is-running* nil))))))
|
||||||
:name "opencortex-tui-reader"))
|
:name "opencortex-tui-reader")))
|
||||||
|
)
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
** Main Entry Point
|
** Main Entry Point
|
||||||
@@ -200,5 +201,5 @@ The OpenCortex TUI Client is a standalone Common Lisp application built on **Cro
|
|||||||
(error (c)
|
(error (c)
|
||||||
(format t "TUI Error: ~a~%" 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
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
(:file "harness/perceive")
|
(:file "harness/perceive")
|
||||||
(:file "harness/reason")
|
(:file "harness/reason")
|
||||||
(:file "harness/act")
|
(:file "harness/act")
|
||||||
|
(:file "harness/doctor")
|
||||||
(:file "harness/loop")))
|
(:file "harness/loop")))
|
||||||
|
|
||||||
(defsystem :opencortex/tests
|
(defsystem :opencortex/tests
|
||||||
|
|||||||
62
scripts/harness-screen.sh
Executable file
62
scripts/harness-screen.sh
Executable file
@@ -0,0 +1,62 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# OpenCortex TUI Harness via GNU Screen
|
||||||
|
# Provides a persistent PTY for Croatoan/ncurses TUI testing.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SESSION="oct-tui"
|
||||||
|
LOG="$HOME/.local/state/opencortex/tui-screen.log"
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
screen -S "$SESSION" -X quit 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-start}" in
|
||||||
|
start)
|
||||||
|
cleanup
|
||||||
|
mkdir -p "$(dirname "$LOG")"
|
||||||
|
export TERM=screen-256color
|
||||||
|
export SKILLS_DIR="$HOME/.local/share/opencortex/skills"
|
||||||
|
screen -dmS "$SESSION" bash -c '
|
||||||
|
sbcl --non-interactive \
|
||||||
|
--eval "(load (merge-pathnames \"quicklisp/setup.lisp\" (user-homedir-pathname)))" \
|
||||||
|
--eval "(push (truename \"$HOME/.local/share/opencortex/\") asdf:*central-registry*)" \
|
||||||
|
--eval "(ql:quickload :opencortex/tui :silent t)" \
|
||||||
|
--eval "(opencortex.tui:main)" \
|
||||||
|
2>&1 | tee '"$LOG"'
|
||||||
|
echo "[TUI exited with code $?]"
|
||||||
|
sleep 3600
|
||||||
|
'
|
||||||
|
sleep 2
|
||||||
|
echo "TUI started in screen session '$SESSION'"
|
||||||
|
echo "Logs: $LOG"
|
||||||
|
;;
|
||||||
|
|
||||||
|
send)
|
||||||
|
shift
|
||||||
|
screen -S "$SESSION" -X stuff "$*"
|
||||||
|
;;
|
||||||
|
|
||||||
|
enter)
|
||||||
|
screen -S "$SESSION" -X stuff "$(printf '\r')"
|
||||||
|
;;
|
||||||
|
|
||||||
|
capture)
|
||||||
|
screen -S "$SESSION" -X hardcopy -h /tmp/oct-tui-capture.txt
|
||||||
|
cat /tmp/oct-tui-capture.txt
|
||||||
|
;;
|
||||||
|
|
||||||
|
log)
|
||||||
|
tail -f "$LOG"
|
||||||
|
;;
|
||||||
|
|
||||||
|
kill)
|
||||||
|
cleanup
|
||||||
|
echo "TUI session killed."
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 {start|send <text>|enter|capture|log|kill}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
89
scripts/test-tui.sh
Executable file
89
scripts/test-tui.sh
Executable file
@@ -0,0 +1,89 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# OpenCortex TUI Automated Test Harness
|
||||||
|
# Runs the TUI in a tmux pane, sends "hi", captures response.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SESSION="opencortex-tui-test"
|
||||||
|
TUI_LOG="/tmp/opencortex-tui-test.log"
|
||||||
|
CAPTURE="/tmp/opencortex-tui-capture.txt"
|
||||||
|
TIMEOUT_SEC=30
|
||||||
|
|
||||||
|
echo "=== OpenCortex TUI Test Harness ==="
|
||||||
|
echo "Log: $TUI_LOG"
|
||||||
|
echo "Capture: $CAPTURE"
|
||||||
|
|
||||||
|
# Clean up any stale session
|
||||||
|
tmux kill-session -t "$SESSION" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Verify daemon is running
|
||||||
|
if ! ss -tln | grep -q ':9105'; then
|
||||||
|
echo "ERROR: Daemon not running on port 9105"
|
||||||
|
echo "Start it with: cd ~/memex/projects/opencortex && ./opencortex.sh daemon"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create tmux session with TUI
|
||||||
|
echo "[1/5] Starting TUI in tmux session '$SESSION'..."
|
||||||
|
tmux new-session -d -s "$SESSION" \
|
||||||
|
-e OC_CONFIG_DIR="$HOME/.config/opencortex" \
|
||||||
|
-e OC_DATA_DIR="$HOME/.local/share/opencortex" \
|
||||||
|
-e SKILLS_DIR="$HOME/.local/share/opencortex/skills" \
|
||||||
|
-e TERM="screen-256color" \
|
||||||
|
"sbcl --non-interactive \
|
||||||
|
--eval '(load (merge-pathnames \"quicklisp/setup.lisp\" (user-homedir-pathname)))' \
|
||||||
|
--eval '(push (truename \"$HOME/.local/share/opencortex/\") asdf:*central-registry*)' \
|
||||||
|
--eval '(ql:quickload :opencortex/tui)' \
|
||||||
|
--eval '(opencortex.tui:main)' 2>&1 | tee $TUI_LOG"
|
||||||
|
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
# Capture initial state
|
||||||
|
tmux capture-pane -t "$SESSION" -p > "$CAPTURE"
|
||||||
|
echo "[2/5] Initial TUI state captured ($(wc -l < "$CAPTURE") lines)"
|
||||||
|
|
||||||
|
# Send message
|
||||||
|
echo "[3/5] Sending 'hi' + Enter..."
|
||||||
|
tmux send-keys -t "$SESSION" "hi" Enter
|
||||||
|
|
||||||
|
# Wait for response
|
||||||
|
echo "[4/5] Waiting up to ${TIMEOUT_SEC}s for response..."
|
||||||
|
for i in $(seq 1 $TIMEOUT_SEC); do
|
||||||
|
tmux capture-pane -t "$SESSION" -p > "$CAPTURE"
|
||||||
|
# Check if daemon response arrived (contains arrow-down marker or actual response text)
|
||||||
|
if grep -qE "(⬇|Hi|Hello|Neural Cascade)" "$CAPTURE"; then
|
||||||
|
echo " ✓ Response detected after ${i}s"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
# Final capture
|
||||||
|
tmux capture-pane -t "$SESSION" -p > "$CAPTURE"
|
||||||
|
echo "[5/5] Final capture ($(wc -l < "$CAPTURE") lines)"
|
||||||
|
|
||||||
|
# Extract and display results
|
||||||
|
echo ""
|
||||||
|
echo "=== SCREEN CAPTURE ==="
|
||||||
|
cat "$CAPTURE"
|
||||||
|
echo ""
|
||||||
|
echo "=== TUI LOG (last 20 lines) ==="
|
||||||
|
tail -20 "$TUI_LOG"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check for errors
|
||||||
|
if grep -qE "(TUI Error|Connection lost|ERROR:)" "$TUI_LOG"; then
|
||||||
|
echo "❌ TEST FAILED: Errors detected in TUI log"
|
||||||
|
tmux kill-session -t "$SESSION" 2>/dev/null || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -qE "(⬇|Hi|Hello)" "$CAPTURE"; then
|
||||||
|
echo "✅ TEST PASSED: Response received from daemon"
|
||||||
|
else
|
||||||
|
echo "⚠️ TEST INCOMPLETE: No response marker found (daemon may have timed out)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
tmux kill-session -t "$SESSION" 2>/dev/null || true
|
||||||
|
echo "Done."
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
(fiveam:test test-tui-connection-drop
|
(fiveam:test test-tui-connection-drop
|
||||||
"Tier 2 Chaos: Verify that handle-return degrades gracefully when the daemon connection is lost."
|
"Tier 2 Chaos: Verify that handle-return degrades gracefully when the daemon connection is lost."
|
||||||
(let ((opencortex.tui::*incoming-msgs* nil)
|
(let ((opencortex.tui::*incoming-msgs* nil)
|
||||||
(opencortex.tui::*input-buffer* (make-array 5 :element-type 'char :initial-contents "hello" :fill-pointer 5 :adjustable t))
|
(opencortex.tui::*input-buffer* (make-array 5 :element-type 'character :initial-contents "hello" :fill-pointer 5 :adjustable t))
|
||||||
;; Create a closed stream to simulate connection drop
|
;; Create a closed stream to simulate connection drop
|
||||||
(mock-stream (make-string-output-stream)))
|
(mock-stream (make-string-output-stream)))
|
||||||
(close mock-stream)
|
(close mock-stream)
|
||||||
|
|||||||
Reference in New Issue
Block a user