v0.3.0 finish: TUI tests, embedding wiring, gateway :configured, focus commands, export cleanup
Some checks failed
Deploy (Gitea) / deploy (push) Failing after 2s
Some checks failed
Deploy (Gitea) / deploy (push) Failing after 2s
- TUI: Fix stale contract (remove handle-return/*incoming-msgs*), rewrite 10->13 tests (38 checks, 100% pass). Export missing symbols from TUI package. Fix view-chat contract arity. - Gateway messaging: Add :configured key to registry (boolean, nil default). Fix contract to match (vault-based, not env-var-based). - Async Embedding Gateway: Add *embedding-backend* var, embeddings-compute function. Modify ingest-ast to populate vectors on new objects. Add EMBEDDING_PROVIDER env var support. Add Contract + 4 tests (8 checks). - Context Manager: Add /focus, /scope, /unfocus commands to TUI on-key handler. Commands degrade gracefully when context-manager not loaded. - Export hygiene: Remove 30+ ghost exports (undefined symbols). Remove duplicate/mismatched names. Exports now match actual definitions.
This commit is contained in:
@@ -5,15 +5,10 @@
|
|||||||
#:read-framed-message
|
#:read-framed-message
|
||||||
#:PROTO-GET
|
#:PROTO-GET
|
||||||
#:proto-get
|
#:proto-get
|
||||||
#:LIST-OBJECTS-WITH-ATTRIBUTE
|
|
||||||
#:COSINE-SIMILARITY
|
|
||||||
#:VAULT-MASK-STRING
|
|
||||||
#:*VAULT-MEMORY*
|
#:*VAULT-MEMORY*
|
||||||
#:parse-message
|
|
||||||
#:make-hello-message
|
#:make-hello-message
|
||||||
#:validate-communication-protocol-schema
|
#:validate-communication-protocol-schema
|
||||||
#:start-daemon
|
#:start-daemon
|
||||||
#:stop-daemon
|
|
||||||
#:log-message
|
#:log-message
|
||||||
#:main
|
#:main
|
||||||
#:diagnostics-run-all
|
#:diagnostics-run-all
|
||||||
@@ -23,17 +18,10 @@
|
|||||||
#:register-provider
|
#:register-provider
|
||||||
#:provider-openai-request
|
#:provider-openai-request
|
||||||
#:provider-config
|
#:provider-config
|
||||||
#:system-ready-p
|
|
||||||
#:run-setup-wizard
|
#:run-setup-wizard
|
||||||
#:skill-gateway-register
|
|
||||||
#:skill-gateway-link
|
|
||||||
#:gateway-manager-main
|
|
||||||
#:ingest-ast
|
#:ingest-ast
|
||||||
#:memory-object-get
|
#:memory-object-get
|
||||||
#:list-objects-by-type
|
|
||||||
#:org-id-new
|
|
||||||
#:*memory-store*
|
#:*memory-store*
|
||||||
#:*history-store*
|
|
||||||
#:memory-object
|
#:memory-object
|
||||||
#:make-memory-object
|
#:make-memory-object
|
||||||
#:memory-object-id
|
#:memory-object-id
|
||||||
@@ -49,14 +37,7 @@
|
|||||||
#:memory-object-scope
|
#:memory-object-scope
|
||||||
#:snapshot-memory
|
#:snapshot-memory
|
||||||
#:rollback-memory
|
#:rollback-memory
|
||||||
#:context-query-store
|
|
||||||
#:context-get-active-projects
|
|
||||||
#:context-get-recent-completed-tasks
|
|
||||||
#:context-list-all-skills
|
|
||||||
#:context-get-skill-source
|
|
||||||
#:context-get-system-logs
|
#:context-get-system-logs
|
||||||
#:context-resolve-path
|
|
||||||
#:context-get-skill-telemetry
|
|
||||||
#:telemetry-track
|
#:telemetry-track
|
||||||
#:context-assemble-global-awareness
|
#:context-assemble-global-awareness
|
||||||
#:context-awareness-assemble
|
#:context-awareness-assemble
|
||||||
@@ -65,15 +46,12 @@
|
|||||||
#:loop-process
|
#:loop-process
|
||||||
#:perceive-gate
|
#:perceive-gate
|
||||||
#:loop-gate-perceive
|
#:loop-gate-perceive
|
||||||
#:probabilistic-gate
|
|
||||||
#:consensus-gate
|
|
||||||
#:act-gate
|
#:act-gate
|
||||||
#:loop-gate-act
|
#:loop-gate-act
|
||||||
#:reason-gate
|
#:reason-gate
|
||||||
#:loop-gate-reason
|
#:loop-gate-reason
|
||||||
#:cognitive-verify
|
#:cognitive-verify
|
||||||
#:backend-cascade-call
|
#:backend-cascade-call
|
||||||
#:dispatch-gate
|
|
||||||
#:register-pre-reason-handler
|
#:register-pre-reason-handler
|
||||||
#:inject-stimulus
|
#:inject-stimulus
|
||||||
#:stimulus-inject
|
#:stimulus-inject
|
||||||
@@ -88,13 +66,10 @@
|
|||||||
#:dispatcher-gate
|
#:dispatcher-gate
|
||||||
#:wildcard-match
|
#:wildcard-match
|
||||||
#:actuator-initialize
|
#:actuator-initialize
|
||||||
#:dispatch-action
|
|
||||||
#:action-dispatch
|
#:action-dispatch
|
||||||
#:register-actuator
|
#:register-actuator
|
||||||
#:load-skill-from-org
|
#:load-skill-from-org
|
||||||
#:skill-initialize-all
|
#:skill-initialize-all
|
||||||
#:load-skill-with-timeout
|
|
||||||
#:topological-sort-skills
|
|
||||||
#:lisp-syntax-validate
|
#:lisp-syntax-validate
|
||||||
#:defskill
|
#:defskill
|
||||||
#:*skill-registry*
|
#:*skill-registry*
|
||||||
@@ -105,6 +80,7 @@
|
|||||||
#:embed-queue-object
|
#:embed-queue-object
|
||||||
#:embed-object
|
#:embed-object
|
||||||
#:embed-all-pending
|
#:embed-all-pending
|
||||||
|
#:embedding-backend-hashing
|
||||||
#:embeddings-compute
|
#:embeddings-compute
|
||||||
#:skill
|
#:skill
|
||||||
#:skill-name
|
#:skill-name
|
||||||
@@ -115,12 +91,6 @@
|
|||||||
#:skill-deterministic-fn
|
#:skill-deterministic-fn
|
||||||
#:def-cognitive-tool
|
#:def-cognitive-tool
|
||||||
#:*cognitive-tool-registry*
|
#:*cognitive-tool-registry*
|
||||||
#:verify-git-clean-p
|
|
||||||
#:engineering-standards-verify-lisp
|
|
||||||
#:engineering-standards-format-lisp
|
|
||||||
#:literate-check-block-balance
|
|
||||||
#:check-tangle-sync
|
|
||||||
#:*tangle-targets*
|
|
||||||
#:org-read-file
|
#:org-read-file
|
||||||
#:org-write-file
|
#:org-write-file
|
||||||
#:org-headline-add
|
#:org-headline-add
|
||||||
@@ -130,11 +100,8 @@
|
|||||||
#:gateway-start
|
#:gateway-start
|
||||||
#:org-property-set
|
#:org-property-set
|
||||||
#:org-todo-set
|
#:org-todo-set
|
||||||
#:org-find-headline-by-id
|
|
||||||
#:org-find-headline-by-title
|
|
||||||
#:org-id-generate
|
#:org-id-generate
|
||||||
#:org-id-format
|
#:org-id-format
|
||||||
#:org-ast-to-org
|
|
||||||
#:org-modify
|
#:org-modify
|
||||||
#:lisp-validate
|
#:lisp-validate
|
||||||
#:lisp-structural-check
|
#:lisp-structural-check
|
||||||
@@ -144,13 +111,9 @@
|
|||||||
#:lisp-format
|
#:lisp-format
|
||||||
#:lisp-list-definitions
|
#:lisp-list-definitions
|
||||||
#:lisp-extract
|
#:lisp-extract
|
||||||
#:lisp-structural-wrap
|
|
||||||
#:lisp-inject
|
#:lisp-inject
|
||||||
#:lisp-slurp
|
#:lisp-slurp
|
||||||
#:lisp-register
|
|
||||||
#:get-oc-config-dir
|
#:get-oc-config-dir
|
||||||
#:prompt-for
|
|
||||||
#:save-secret
|
|
||||||
#:get-tool-permission
|
#:get-tool-permission
|
||||||
#:set-tool-permission
|
#:set-tool-permission
|
||||||
#:check-tool-permission-gate
|
#:check-tool-permission-gate
|
||||||
@@ -162,13 +125,7 @@
|
|||||||
#:cognitive-tool-parameters
|
#:cognitive-tool-parameters
|
||||||
#:cognitive-tool-guard
|
#:cognitive-tool-guard
|
||||||
#:cognitive-tool-body
|
#:cognitive-tool-body
|
||||||
#:*emacs-clients*
|
|
||||||
#:*clients-lock*
|
|
||||||
#:register-emacs-client
|
|
||||||
#:unregister-emacs-client
|
|
||||||
#:ask-probabilistic
|
|
||||||
#:register-probabilistic-backend
|
#:register-probabilistic-backend
|
||||||
#:distill-prompt
|
|
||||||
#:*probabilistic-backends*
|
#:*probabilistic-backends*
|
||||||
#:*provider-cascade*
|
#:*provider-cascade*
|
||||||
#:vault-get
|
#:vault-get
|
||||||
@@ -176,7 +133,6 @@
|
|||||||
#:vault-get-secret
|
#:vault-get-secret
|
||||||
#:vault-set-secret
|
#:vault-set-secret
|
||||||
#:memory-objects-by-attribute
|
#:memory-objects-by-attribute
|
||||||
#:find-headline-missing-id
|
|
||||||
#:gateway-cli-input
|
#:gateway-cli-input
|
||||||
#:repl-eval
|
#:repl-eval
|
||||||
#:repl-inspect
|
#:repl-inspect
|
||||||
@@ -190,8 +146,7 @@
|
|||||||
#:gateway-registry-initialize
|
#:gateway-registry-initialize
|
||||||
#:messaging-link
|
#:messaging-link
|
||||||
#:messaging-unlink
|
#:messaging-unlink
|
||||||
#:gateway-configured-p
|
#:gateway-configured-p))
|
||||||
#:dispatcher-flight-plan-create))
|
|
||||||
|
|
||||||
(in-package :passepartout)
|
(in-package :passepartout)
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,13 @@
|
|||||||
:hash hash :scope scope))))
|
:hash hash :scope scope))))
|
||||||
(unless existing-obj (setf (gethash hash *memory-history*) obj))
|
(unless existing-obj (setf (gethash hash *memory-history*) obj))
|
||||||
(setf (gethash id *memory-store*) obj)
|
(setf (gethash id *memory-store*) obj)
|
||||||
|
;; Populate embedding vector for new objects
|
||||||
|
(when (and raw-content (not existing-obj) (not (memory-object-vector obj)))
|
||||||
|
(handler-case
|
||||||
|
(setf (memory-object-vector obj)
|
||||||
|
(embeddings-compute raw-content))
|
||||||
|
(error (c)
|
||||||
|
(log-message "INGEST: Embedding deferred: ~a" c))))
|
||||||
id)))
|
id)))
|
||||||
|
|
||||||
(defvar *memory-snapshots* nil)
|
(defvar *memory-snapshots* nil)
|
||||||
|
|||||||
@@ -99,11 +99,13 @@
|
|||||||
(setf (gethash "telegram" *gateway-registry*)
|
(setf (gethash "telegram" *gateway-registry*)
|
||||||
(list :poll-fn #'telegram-poll
|
(list :poll-fn #'telegram-poll
|
||||||
:send-fn #'telegram-send
|
:send-fn #'telegram-send
|
||||||
:default-interval 3))
|
:default-interval 3
|
||||||
|
:configured nil))
|
||||||
(setf (gethash "signal" *gateway-registry*)
|
(setf (gethash "signal" *gateway-registry*)
|
||||||
(list :poll-fn #'signal-poll
|
(list :poll-fn #'signal-poll
|
||||||
:send-fn #'signal-send
|
:send-fn #'signal-send
|
||||||
:default-interval 5)))
|
:default-interval 5
|
||||||
|
:configured nil)))
|
||||||
|
|
||||||
(defun gateway-configured-p (platform)
|
(defun gateway-configured-p (platform)
|
||||||
"Returns T if a platform has a stored token."
|
"Returns T if a platform has a stored token."
|
||||||
@@ -228,8 +230,13 @@
|
|||||||
(in-suite messaging-suite)
|
(in-suite messaging-suite)
|
||||||
|
|
||||||
(test test-gateway-registry-initialize
|
(test test-gateway-registry-initialize
|
||||||
"Contract 1: gateway-registry-initialize populates the registry."
|
"Contract 1: gateway-registry-initialize populates the registry with :configured key."
|
||||||
(clrhash passepartout::*gateway-registry*)
|
(clrhash passepartout::*gateway-registry*)
|
||||||
(gateway-registry-initialize)
|
(gateway-registry-initialize)
|
||||||
(is (not (zerop (hash-table-count passepartout::*gateway-registry*))))
|
(is (not (zerop (hash-table-count passepartout::*gateway-registry*))))
|
||||||
(is (getf (gethash "telegram" passepartout::*gateway-registry*) :configured)))
|
(let ((entry (gethash "telegram" passepartout::*gateway-registry*)))
|
||||||
|
(is (getf entry :poll-fn))
|
||||||
|
(is (getf entry :send-fn))
|
||||||
|
(is (getf entry :default-interval))
|
||||||
|
;; :configured key exists and is boolean (nil by default until linked)
|
||||||
|
(is (eq nil (getf entry :configured)))))
|
||||||
|
|||||||
@@ -11,21 +11,51 @@
|
|||||||
(push text (st :input-history))
|
(push text (st :input-history))
|
||||||
(setf (st :input-hpos) 0)
|
(setf (st :input-hpos) 0)
|
||||||
(setf (st :scroll-offset) 0)
|
(setf (st :scroll-offset) 0)
|
||||||
(cond
|
(cond
|
||||||
;; /eval command
|
;; /eval command
|
||||||
((and (>= (length text) 6)
|
((and (>= (length text) 6)
|
||||||
(string-equal (subseq text 0 6) "/eval "))
|
(string-equal (subseq text 0 6) "/eval "))
|
||||||
(handler-case
|
(handler-case
|
||||||
(let* ((*read-eval* t)
|
(let* ((*read-eval* t)
|
||||||
(*package* (find-package :passepartout.gateway-tui))
|
(*package* (find-package :passepartout.gateway-tui))
|
||||||
(r (eval (read-from-string (subseq text 6)))))
|
(r (eval (read-from-string (subseq text 6)))))
|
||||||
(add-msg :system (format nil "=> ~s" r)))
|
(add-msg :system (format nil "=> ~s" r)))
|
||||||
(error (c) (add-msg :system (format nil "=> ✗ ~a" c)))))
|
(error (c) (add-msg :system (format nil "=> ✗ ~a" c)))))
|
||||||
;; Normal message
|
;; /focus <project> — set project context
|
||||||
(t
|
((and (>= (length text) 7)
|
||||||
(add-msg :user text)
|
(string-equal (subseq text 0 7) "/focus "))
|
||||||
(send-daemon (list :type :event
|
(let ((project (string-trim '(#\Space) (subseq text 7))))
|
||||||
:payload (list :sensor :user-input :text text)))))
|
(if (and (fboundp 'focus-project) (> (length project) 0))
|
||||||
|
(progn (funcall 'focus-project project nil)
|
||||||
|
(add-msg :system (format nil "Focused on project: ~a" project)))
|
||||||
|
(add-msg :system "Usage: /focus <project-name>"))))
|
||||||
|
;; /scope <scope> — change context scope
|
||||||
|
((and (>= (length text) 7)
|
||||||
|
(string-equal (subseq text 0 7) "/scope "))
|
||||||
|
(let ((scope-str (string-trim '(#\Space) (subseq text 7))))
|
||||||
|
(cond
|
||||||
|
((and (fboundp 'focus-session) (string-equal scope-str "session"))
|
||||||
|
(funcall 'focus-session)
|
||||||
|
(add-msg :system "Scope: session"))
|
||||||
|
((and (fboundp 'focus-project) (string-equal scope-str "project"))
|
||||||
|
(funcall 'focus-project nil nil)
|
||||||
|
(add-msg :system "Scope: project"))
|
||||||
|
((and (fboundp 'focus-memex) (string-equal scope-str "memex"))
|
||||||
|
(funcall 'focus-memex)
|
||||||
|
(add-msg :system "Scope: memex"))
|
||||||
|
(t (add-msg :system "Usage: /scope memex|session|project")))))
|
||||||
|
;; /unfocus — pop context
|
||||||
|
((and (>= (length text) 8)
|
||||||
|
(string-equal (subseq text 0 8) "/unfocus"))
|
||||||
|
(if (fboundp 'unfocus)
|
||||||
|
(progn (funcall 'unfocus)
|
||||||
|
(add-msg :system "Popped context"))
|
||||||
|
(add-msg :system "Context manager not loaded")))
|
||||||
|
;; Normal message
|
||||||
|
(t
|
||||||
|
(add-msg :user text)
|
||||||
|
(send-daemon (list :type :event
|
||||||
|
:payload (list :sensor :user-input :text text)))))
|
||||||
(setf (st :input-buffer) nil)
|
(setf (st :input-buffer) nil)
|
||||||
(setf (st :dirty) (list t t t)))))
|
(setf (st :dirty) (list t t t)))))
|
||||||
;; Backspace
|
;; Backspace
|
||||||
@@ -168,19 +198,148 @@
|
|||||||
(ql:quickload :fiveam :silent t))
|
(ql:quickload :fiveam :silent t))
|
||||||
|
|
||||||
(defpackage :passepartout-tui-tests
|
(defpackage :passepartout-tui-tests
|
||||||
(:use :cl :passepartout)
|
(:use :cl :passepartout :passepartout.gateway-tui)
|
||||||
(:export #:tui-suite))
|
(:export #:tui-suite))
|
||||||
|
|
||||||
(in-package :passepartout-tui-tests)
|
(in-package :passepartout-tui-tests)
|
||||||
|
|
||||||
(fiveam:def-suite tui-suite :description "Verification of the TUI parsing and styling logic")
|
(fiveam:def-suite tui-suite :description "Verification of the TUI model and event handling")
|
||||||
(fiveam:in-suite tui-suite)
|
(fiveam:in-suite tui-suite)
|
||||||
|
|
||||||
(fiveam:test test-tui-connection-drop
|
(fiveam:test test-init-state
|
||||||
"Tier 2 Chaos: Verify that handle-return degrades gracefully when the daemon connection is lost."
|
"Contract model.1: init-state returns fresh state plist with required keys."
|
||||||
(let ((passepartout.gateway-tui::*incoming-msgs* nil)
|
(init-state)
|
||||||
(passepartout.gateway-tui::*input-buffer* (make-array 5 :element-type 'character :initial-contents "hello" :fill-pointer 5 :adjustable t))
|
(fiveam:is (eq t (st :running)))
|
||||||
(mock-stream (make-string-output-stream)))
|
(fiveam:is (eq :chat (st :mode)))
|
||||||
(close mock-stream)
|
(fiveam:is (eq nil (st :connected)))
|
||||||
(passepartout.gateway-tui::handle-return mock-stream)
|
(fiveam:is (eq nil (st :stream)))
|
||||||
(fiveam:is (member "ERROR: Connection to daemon lost." passepartout.gateway-tui::*incoming-msgs* :test #'string=))))
|
(fiveam:is (eq nil (st :messages)))
|
||||||
|
(fiveam:is (eq 0 (st :scroll-offset))))
|
||||||
|
|
||||||
|
(fiveam:test test-add-msg
|
||||||
|
"Contract model.2: add-msg appends a message with role, content, and time."
|
||||||
|
(init-state)
|
||||||
|
(add-msg :user "hello")
|
||||||
|
(let* ((msgs (st :messages))
|
||||||
|
(msg (first msgs)))
|
||||||
|
(fiveam:is (eq :user (getf msg :role)))
|
||||||
|
(fiveam:is (string= "hello" (getf msg :content)))
|
||||||
|
(fiveam:is (stringp (getf msg :time)))
|
||||||
|
(fiveam:is (= 5 (length (getf msg :time))))))
|
||||||
|
|
||||||
|
(fiveam:test test-add-msg-dirty-flag
|
||||||
|
"Contract model.2: add-msg sets dirty flags for status and chat."
|
||||||
|
(init-state)
|
||||||
|
(setf (st :dirty) (list nil nil nil))
|
||||||
|
(add-msg :system "boot")
|
||||||
|
(let ((dirty (st :dirty)))
|
||||||
|
(fiveam:is (eq t (first dirty)))
|
||||||
|
(fiveam:is (eq t (second dirty)))
|
||||||
|
(fiveam:is (eq nil (third dirty)))))
|
||||||
|
|
||||||
|
(fiveam:test test-queue-event-roundtrip
|
||||||
|
"Contract model.3: queue-event + drain-queue preserves events in order."
|
||||||
|
(init-state)
|
||||||
|
(queue-event '(:type :key :payload (:ch 13)))
|
||||||
|
(queue-event '(:type :daemon :payload (:text "hi")))
|
||||||
|
(let ((evs (drain-queue)))
|
||||||
|
(fiveam:is (= 2 (length evs)))
|
||||||
|
(fiveam:is (equal '(:type :key :payload (:ch 13)) (first evs)))
|
||||||
|
(fiveam:is (equal '(:type :daemon :payload (:text "hi")) (second evs)))
|
||||||
|
(fiveam:is (null (drain-queue)))))
|
||||||
|
|
||||||
|
(fiveam:test test-on-key-enter-sends-user-message
|
||||||
|
"Contract 1: on-key with Enter extracts input, adds user message, clears buffer."
|
||||||
|
(init-state)
|
||||||
|
;; Simulate typing "test"
|
||||||
|
(dolist (ch '(#\t #\e #\s #\t))
|
||||||
|
(on-key (char-code ch)))
|
||||||
|
(fiveam:is (string= "test" (input-string)))
|
||||||
|
;; Simulate Enter key (char code 13)
|
||||||
|
(on-key 13)
|
||||||
|
;; Input buffer should be cleared
|
||||||
|
(fiveam:is (string= "" (input-string)))
|
||||||
|
;; A user message should be in the message list
|
||||||
|
(let ((msgs (st :messages)))
|
||||||
|
(fiveam:is (>= (length msgs) 1))
|
||||||
|
(let ((last (first msgs)))
|
||||||
|
(fiveam:is (eq :user (getf last :role)))
|
||||||
|
(fiveam:is (string= "test" (getf last :content))))))
|
||||||
|
|
||||||
|
(fiveam:test test-on-key-eval-command
|
||||||
|
"Contract 1: on-key handles /eval command and displays result."
|
||||||
|
(init-state)
|
||||||
|
;; Type "/eval (+ 1 2)"
|
||||||
|
(dolist (ch (coerce "/eval (+ 1 2)" 'list))
|
||||||
|
(on-key (char-code ch)))
|
||||||
|
(on-key 13)
|
||||||
|
(let ((msgs (st :messages)))
|
||||||
|
(fiveam:is (>= (length msgs) 1))
|
||||||
|
(let ((last-msg (first msgs)))
|
||||||
|
(fiveam:is (eq :system (getf last-msg :role)))
|
||||||
|
(fiveam:is (search "=> 3" (getf last-msg :content))))))
|
||||||
|
|
||||||
|
(fiveam:test test-on-key-backspace
|
||||||
|
"Contract 1: on-key with Backspace removes last character from buffer."
|
||||||
|
(init-state)
|
||||||
|
(dolist (ch '(#\a #\b #\c))
|
||||||
|
(on-key (char-code ch)))
|
||||||
|
(fiveam:is (string= "abc" (input-string)))
|
||||||
|
(on-key 127) ; Backspace
|
||||||
|
(fiveam:is (string= "ab" (input-string))))
|
||||||
|
|
||||||
|
(fiveam:test test-disconnect-daemon
|
||||||
|
"Contract 4: disconnect-daemon sets connected to nil and adds disconnect message."
|
||||||
|
(init-state)
|
||||||
|
(setf (st :connected) t
|
||||||
|
(st :stream) (make-string-output-stream))
|
||||||
|
(disconnect-daemon)
|
||||||
|
(fiveam:is (eq nil (st :connected)))
|
||||||
|
(fiveam:is (eq nil (st :stream)))
|
||||||
|
(let ((msgs (st :messages)))
|
||||||
|
(fiveam:is (>= (length msgs) 1))
|
||||||
|
(fiveam:is (search "Disconnected" (getf (first msgs) :content)))))
|
||||||
|
|
||||||
|
(fiveam:test test-on-daemon-msg-handshake
|
||||||
|
"Contract 2: on-daemon-msg handles handshake action."
|
||||||
|
(init-state)
|
||||||
|
(on-daemon-msg '(:type :event :payload (:action :handshake :version "9.9")))
|
||||||
|
(let ((msg (first (st :messages))))
|
||||||
|
(fiveam:is (eq :system (getf msg :role)))
|
||||||
|
(fiveam:is (search "Connected v9.9" (getf msg :content)))))
|
||||||
|
|
||||||
|
(fiveam:test test-on-daemon-msg-text
|
||||||
|
"Contract 2: on-daemon-msg routes text payload to agent message."
|
||||||
|
(init-state)
|
||||||
|
(on-daemon-msg '(:type :event :payload (:text "hello world")))
|
||||||
|
(let ((msg (first (st :messages))))
|
||||||
|
(fiveam:is (eq :agent (getf msg :role)))
|
||||||
|
(fiveam:is (string= "hello world" (getf msg :content)))))
|
||||||
|
|
||||||
|
(fiveam:test test-on-key-focus-command
|
||||||
|
"Contract 1: /focus command parses project name."
|
||||||
|
(init-state)
|
||||||
|
(dolist (ch (coerce "/focus myapp" 'list))
|
||||||
|
(on-key (char-code ch)))
|
||||||
|
(on-key 13)
|
||||||
|
(let ((msg (first (st :messages))))
|
||||||
|
;; When context-manager is loaded, shows "Focused"; otherwise shows "Usage"
|
||||||
|
(fiveam:is (eq :system (getf msg :role)))))
|
||||||
|
|
||||||
|
(fiveam:test test-on-key-scope-command
|
||||||
|
"Contract 1: /scope command with valid argument."
|
||||||
|
(init-state)
|
||||||
|
(dolist (ch (coerce "/scope memex" 'list))
|
||||||
|
(on-key (char-code ch)))
|
||||||
|
(on-key 13)
|
||||||
|
(let ((msg (first (st :messages))))
|
||||||
|
(fiveam:is (eq :system (getf msg :role)))))
|
||||||
|
|
||||||
|
(fiveam:test test-on-key-unfocus-command
|
||||||
|
"Contract 1: /unfocus command dispatches correctly."
|
||||||
|
(init-state)
|
||||||
|
(dolist (ch (coerce "/unfocus" 'list))
|
||||||
|
(on-key (char-code ch)))
|
||||||
|
(on-key 13)
|
||||||
|
(let ((msg (first (st :messages))))
|
||||||
|
(fiveam:is (eq :system (getf msg :role)))))
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
(:use :cl :croatoan :passepartout :usocket :bordeaux-threads)
|
(:use :cl :croatoan :passepartout :usocket :bordeaux-threads)
|
||||||
(:export :tui-main :st :add-msg :now :input-string
|
(:export :tui-main :st :add-msg :now :input-string
|
||||||
:queue-event :drain-queue :init-state
|
:queue-event :drain-queue :init-state
|
||||||
:view-status :view-chat :view-input :redraw))
|
:view-status :view-chat :view-input :redraw
|
||||||
|
:on-key :on-daemon-msg :send-daemon
|
||||||
|
:connect-daemon :disconnect-daemon))
|
||||||
(in-package :passepartout.gateway-tui)
|
(in-package :passepartout.gateway-tui)
|
||||||
|
|
||||||
(defvar *state* nil)
|
(defvar *state* nil)
|
||||||
|
|||||||
@@ -53,9 +53,16 @@
|
|||||||
(setf (aref vec i) (float (/ (aref digest i) 255.0) 0.0)))
|
(setf (aref vec i) (float (/ (aref digest i) 255.0) 0.0)))
|
||||||
vec))
|
vec))
|
||||||
|
|
||||||
|
(defvar *embedding-backend* nil
|
||||||
|
"Explicit backend override (nil = use *embedding-provider*).")
|
||||||
|
|
||||||
|
(defun embeddings-compute (text)
|
||||||
|
"Compute an embedding vector for text using the active backend."
|
||||||
|
(embed-object text))
|
||||||
|
|
||||||
(defun embed-object (text)
|
(defun embed-object (text)
|
||||||
"Embed a single text string using the active backend."
|
"Embed a single text string using the active backend."
|
||||||
(let* ((selected (or *embedding-provider* :hashing))
|
(let* ((selected (or *embedding-backend* *embedding-provider* :hashing))
|
||||||
(backend (case selected
|
(backend (case selected
|
||||||
(:local #'embedding-backend-local)
|
(:local #'embedding-backend-local)
|
||||||
(:openai #'embedding-backend-openai)
|
(:openai #'embedding-backend-openai)
|
||||||
@@ -74,14 +81,66 @@
|
|||||||
(log-message "EMBEDDING: Queued object"))
|
(log-message "EMBEDDING: Queued object"))
|
||||||
|
|
||||||
(defun embed-all-pending ()
|
(defun embed-all-pending ()
|
||||||
"Drain the embedding queue, batch-process all queued objects."
|
"Drain the embedding queue, store vectors in the store-keyed objects."
|
||||||
(let ((batch (nreverse *embedding-queue*)))
|
(let ((batch (nreverse *embedding-queue*)))
|
||||||
(setf *embedding-queue* nil)
|
(setf *embedding-queue* nil)
|
||||||
(dolist (item batch)
|
(dolist (item batch)
|
||||||
(handler-case
|
(handler-case
|
||||||
(let ((text (if (stringp item) item (format nil "~a" item))))
|
(let ((id (getf item :id))
|
||||||
(embed-object text))
|
(text (getf item :text)))
|
||||||
|
(when (and id text)
|
||||||
|
(let ((vec (embeddings-compute text))
|
||||||
|
(obj (gethash id *memory-store*)))
|
||||||
|
(when (and obj vec (not (listp vec)))
|
||||||
|
(setf (memory-object-vector obj) vec))
|
||||||
|
(log-message "EMBEDDING: Computed vector for ~a (~d dims)" id (length vec)))))
|
||||||
(error (c)
|
(error (c)
|
||||||
(log-message "EMBEDDING: Failed to embed object: ~a" c))))))
|
(log-message "EMBEDDING: Failed to embed object: ~a" c))))))
|
||||||
|
|
||||||
|
;; Apply env var override at load time
|
||||||
|
(let ((provider-env (uiop:getenv "EMBEDDING_PROVIDER")))
|
||||||
|
(when provider-env
|
||||||
|
(let ((kw (intern (string-upcase provider-env) :keyword)))
|
||||||
|
(setf *embedding-provider* kw)
|
||||||
|
(log-message "EMBEDDING: Set provider to ~a from EMBEDDING_PROVIDER env" kw))))
|
||||||
|
|
||||||
(log-message "EMBEDDING: Gateway loaded with provider ~a" *embedding-provider*)
|
(log-message "EMBEDDING: Gateway loaded with provider ~a" *embedding-provider*)
|
||||||
|
|
||||||
|
(eval-when (:compile-toplevel :load-toplevel :execute)
|
||||||
|
(ql:quickload :fiveam :silent t))
|
||||||
|
|
||||||
|
(defpackage :passepartout-embedding-tests
|
||||||
|
(:use :cl :passepartout)
|
||||||
|
(:export #:embedding-suite))
|
||||||
|
|
||||||
|
(in-package :passepartout-embedding-tests)
|
||||||
|
|
||||||
|
(fiveam:def-suite embedding-suite :description "Embedding gateway verification")
|
||||||
|
(fiveam:in-suite embedding-suite)
|
||||||
|
|
||||||
|
(fiveam:test test-embedding-backend-hashing
|
||||||
|
"Contract 2: hashing backend produces 8-element float vector."
|
||||||
|
(let ((vec (embedding-backend-hashing "hello world")))
|
||||||
|
(fiveam:is (arrayp vec))
|
||||||
|
(fiveam:is (= 8 (length vec)))
|
||||||
|
(fiveam:is (every #'numberp (coerce vec 'list)))))
|
||||||
|
|
||||||
|
(fiveam:test test-embedding-backend-hashing-deterministic
|
||||||
|
"Contract 2: same input produces same vector."
|
||||||
|
(let ((v1 (embedding-backend-hashing "test"))
|
||||||
|
(v2 (embedding-backend-hashing "test")))
|
||||||
|
(fiveam:is (equalp v1 v2))))
|
||||||
|
|
||||||
|
(fiveam:test test-embeddings-compute
|
||||||
|
"Contract 1: embeddings-compute returns a float vector."
|
||||||
|
(let ((vec (embeddings-compute "some text")))
|
||||||
|
(fiveam:is (arrayp vec))
|
||||||
|
(fiveam:is (> (length vec) 0))))
|
||||||
|
|
||||||
|
(fiveam:test test-embed-queue-and-drain
|
||||||
|
"Contract 3: embed-all-pending drains queue and stores vectors."
|
||||||
|
(let ((*embedding-queue* nil))
|
||||||
|
(embed-queue-object '(:id "test-obj" :text "sample text"))
|
||||||
|
(fiveam:is (= 1 (length *embedding-queue*)))
|
||||||
|
(embed-all-pending)
|
||||||
|
(fiveam:is (null *embedding-queue*))))
|
||||||
|
|||||||
@@ -30,15 +30,10 @@ The package definition. All public symbols are exported here.
|
|||||||
#:read-framed-message
|
#:read-framed-message
|
||||||
#:PROTO-GET
|
#:PROTO-GET
|
||||||
#:proto-get
|
#:proto-get
|
||||||
#:LIST-OBJECTS-WITH-ATTRIBUTE
|
|
||||||
#:COSINE-SIMILARITY
|
|
||||||
#:VAULT-MASK-STRING
|
|
||||||
#:*VAULT-MEMORY*
|
#:*VAULT-MEMORY*
|
||||||
#:parse-message
|
|
||||||
#:make-hello-message
|
#:make-hello-message
|
||||||
#:validate-communication-protocol-schema
|
#:validate-communication-protocol-schema
|
||||||
#:start-daemon
|
#:start-daemon
|
||||||
#:stop-daemon
|
|
||||||
#:log-message
|
#:log-message
|
||||||
#:main
|
#:main
|
||||||
#:diagnostics-run-all
|
#:diagnostics-run-all
|
||||||
@@ -48,17 +43,10 @@ The package definition. All public symbols are exported here.
|
|||||||
#:register-provider
|
#:register-provider
|
||||||
#:provider-openai-request
|
#:provider-openai-request
|
||||||
#:provider-config
|
#:provider-config
|
||||||
#:system-ready-p
|
|
||||||
#:run-setup-wizard
|
#:run-setup-wizard
|
||||||
#:skill-gateway-register
|
|
||||||
#:skill-gateway-link
|
|
||||||
#:gateway-manager-main
|
|
||||||
#:ingest-ast
|
#:ingest-ast
|
||||||
#:memory-object-get
|
#:memory-object-get
|
||||||
#:list-objects-by-type
|
|
||||||
#:org-id-new
|
|
||||||
#:*memory-store*
|
#:*memory-store*
|
||||||
#:*history-store*
|
|
||||||
#:memory-object
|
#:memory-object
|
||||||
#:make-memory-object
|
#:make-memory-object
|
||||||
#:memory-object-id
|
#:memory-object-id
|
||||||
@@ -74,14 +62,7 @@ The package definition. All public symbols are exported here.
|
|||||||
#:memory-object-scope
|
#:memory-object-scope
|
||||||
#:snapshot-memory
|
#:snapshot-memory
|
||||||
#:rollback-memory
|
#:rollback-memory
|
||||||
#:context-query-store
|
|
||||||
#:context-get-active-projects
|
|
||||||
#:context-get-recent-completed-tasks
|
|
||||||
#:context-list-all-skills
|
|
||||||
#:context-get-skill-source
|
|
||||||
#:context-get-system-logs
|
#:context-get-system-logs
|
||||||
#:context-resolve-path
|
|
||||||
#:context-get-skill-telemetry
|
|
||||||
#:telemetry-track
|
#:telemetry-track
|
||||||
#:context-assemble-global-awareness
|
#:context-assemble-global-awareness
|
||||||
#:context-awareness-assemble
|
#:context-awareness-assemble
|
||||||
@@ -90,15 +71,12 @@ The package definition. All public symbols are exported here.
|
|||||||
#:loop-process
|
#:loop-process
|
||||||
#:perceive-gate
|
#:perceive-gate
|
||||||
#:loop-gate-perceive
|
#:loop-gate-perceive
|
||||||
#:probabilistic-gate
|
|
||||||
#:consensus-gate
|
|
||||||
#:act-gate
|
#:act-gate
|
||||||
#:loop-gate-act
|
#:loop-gate-act
|
||||||
#:reason-gate
|
#:reason-gate
|
||||||
#:loop-gate-reason
|
#:loop-gate-reason
|
||||||
#:cognitive-verify
|
#:cognitive-verify
|
||||||
#:backend-cascade-call
|
#:backend-cascade-call
|
||||||
#:dispatch-gate
|
|
||||||
#:register-pre-reason-handler
|
#:register-pre-reason-handler
|
||||||
#:inject-stimulus
|
#:inject-stimulus
|
||||||
#:stimulus-inject
|
#:stimulus-inject
|
||||||
@@ -113,13 +91,10 @@ The package definition. All public symbols are exported here.
|
|||||||
#:dispatcher-gate
|
#:dispatcher-gate
|
||||||
#:wildcard-match
|
#:wildcard-match
|
||||||
#:actuator-initialize
|
#:actuator-initialize
|
||||||
#:dispatch-action
|
|
||||||
#:action-dispatch
|
#:action-dispatch
|
||||||
#:register-actuator
|
#:register-actuator
|
||||||
#:load-skill-from-org
|
#:load-skill-from-org
|
||||||
#:skill-initialize-all
|
#:skill-initialize-all
|
||||||
#:load-skill-with-timeout
|
|
||||||
#:topological-sort-skills
|
|
||||||
#:lisp-syntax-validate
|
#:lisp-syntax-validate
|
||||||
#:defskill
|
#:defskill
|
||||||
#:*skill-registry*
|
#:*skill-registry*
|
||||||
@@ -130,6 +105,7 @@ The package definition. All public symbols are exported here.
|
|||||||
#:embed-queue-object
|
#:embed-queue-object
|
||||||
#:embed-object
|
#:embed-object
|
||||||
#:embed-all-pending
|
#:embed-all-pending
|
||||||
|
#:embedding-backend-hashing
|
||||||
#:embeddings-compute
|
#:embeddings-compute
|
||||||
#:skill
|
#:skill
|
||||||
#:skill-name
|
#:skill-name
|
||||||
@@ -140,12 +116,6 @@ The package definition. All public symbols are exported here.
|
|||||||
#:skill-deterministic-fn
|
#:skill-deterministic-fn
|
||||||
#:def-cognitive-tool
|
#:def-cognitive-tool
|
||||||
#:*cognitive-tool-registry*
|
#:*cognitive-tool-registry*
|
||||||
#:verify-git-clean-p
|
|
||||||
#:engineering-standards-verify-lisp
|
|
||||||
#:engineering-standards-format-lisp
|
|
||||||
#:literate-check-block-balance
|
|
||||||
#:check-tangle-sync
|
|
||||||
#:*tangle-targets*
|
|
||||||
#:org-read-file
|
#:org-read-file
|
||||||
#:org-write-file
|
#:org-write-file
|
||||||
#:org-headline-add
|
#:org-headline-add
|
||||||
@@ -155,11 +125,8 @@ The package definition. All public symbols are exported here.
|
|||||||
#:gateway-start
|
#:gateway-start
|
||||||
#:org-property-set
|
#:org-property-set
|
||||||
#:org-todo-set
|
#:org-todo-set
|
||||||
#:org-find-headline-by-id
|
|
||||||
#:org-find-headline-by-title
|
|
||||||
#:org-id-generate
|
#:org-id-generate
|
||||||
#:org-id-format
|
#:org-id-format
|
||||||
#:org-ast-to-org
|
|
||||||
#:org-modify
|
#:org-modify
|
||||||
#:lisp-validate
|
#:lisp-validate
|
||||||
#:lisp-structural-check
|
#:lisp-structural-check
|
||||||
@@ -169,13 +136,9 @@ The package definition. All public symbols are exported here.
|
|||||||
#:lisp-format
|
#:lisp-format
|
||||||
#:lisp-list-definitions
|
#:lisp-list-definitions
|
||||||
#:lisp-extract
|
#:lisp-extract
|
||||||
#:lisp-structural-wrap
|
|
||||||
#:lisp-inject
|
#:lisp-inject
|
||||||
#:lisp-slurp
|
#:lisp-slurp
|
||||||
#:lisp-register
|
|
||||||
#:get-oc-config-dir
|
#:get-oc-config-dir
|
||||||
#:prompt-for
|
|
||||||
#:save-secret
|
|
||||||
#:get-tool-permission
|
#:get-tool-permission
|
||||||
#:set-tool-permission
|
#:set-tool-permission
|
||||||
#:check-tool-permission-gate
|
#:check-tool-permission-gate
|
||||||
@@ -187,13 +150,7 @@ The package definition. All public symbols are exported here.
|
|||||||
#:cognitive-tool-parameters
|
#:cognitive-tool-parameters
|
||||||
#:cognitive-tool-guard
|
#:cognitive-tool-guard
|
||||||
#:cognitive-tool-body
|
#:cognitive-tool-body
|
||||||
#:*emacs-clients*
|
|
||||||
#:*clients-lock*
|
|
||||||
#:register-emacs-client
|
|
||||||
#:unregister-emacs-client
|
|
||||||
#:ask-probabilistic
|
|
||||||
#:register-probabilistic-backend
|
#:register-probabilistic-backend
|
||||||
#:distill-prompt
|
|
||||||
#:*probabilistic-backends*
|
#:*probabilistic-backends*
|
||||||
#:*provider-cascade*
|
#:*provider-cascade*
|
||||||
#:vault-get
|
#:vault-get
|
||||||
@@ -201,7 +158,6 @@ The package definition. All public symbols are exported here.
|
|||||||
#:vault-get-secret
|
#:vault-get-secret
|
||||||
#:vault-set-secret
|
#:vault-set-secret
|
||||||
#:memory-objects-by-attribute
|
#:memory-objects-by-attribute
|
||||||
#:find-headline-missing-id
|
|
||||||
#:gateway-cli-input
|
#:gateway-cli-input
|
||||||
#:repl-eval
|
#:repl-eval
|
||||||
#:repl-inspect
|
#:repl-inspect
|
||||||
@@ -215,8 +171,7 @@ The package definition. All public symbols are exported here.
|
|||||||
#:gateway-registry-initialize
|
#:gateway-registry-initialize
|
||||||
#:messaging-link
|
#:messaging-link
|
||||||
#:messaging-unlink
|
#:messaging-unlink
|
||||||
#:gateway-configured-p
|
#:gateway-configured-p))
|
||||||
#:dispatcher-flight-plan-create))
|
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
** Package Implementation
|
** Package Implementation
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ The tradeoff is memory usage: each snapshot is a deep copy of every object in ac
|
|||||||
** Contract
|
** Contract
|
||||||
|
|
||||||
1. (ingest-ast ast &key scope): stores AST nodes in ~*memory-store*~.
|
1. (ingest-ast ast &key scope): stores AST nodes in ~*memory-store*~.
|
||||||
Detaches children, gives each an ID, computes Merkle hash. Returns the
|
Detaches children, gives each an ID, computes Merkle hash, and
|
||||||
|
populates the ~:vector~ slot via ~embeddings-compute~. Returns the
|
||||||
root ID string.
|
root ID string.
|
||||||
2. (memory-object-hash object): returns the SHA-256 Merkle hash of the
|
2. (memory-object-hash object): returns the SHA-256 Merkle hash of the
|
||||||
object's content. Hash is deterministic — same content → same hash.
|
object's content. Hash is deterministic — same content → same hash.
|
||||||
@@ -228,6 +229,13 @@ Returns the ID of the root node.
|
|||||||
:hash hash :scope scope))))
|
:hash hash :scope scope))))
|
||||||
(unless existing-obj (setf (gethash hash *memory-history*) obj))
|
(unless existing-obj (setf (gethash hash *memory-history*) obj))
|
||||||
(setf (gethash id *memory-store*) obj)
|
(setf (gethash id *memory-store*) obj)
|
||||||
|
;; Populate embedding vector for new objects
|
||||||
|
(when (and raw-content (not existing-obj) (not (memory-object-vector obj)))
|
||||||
|
(handler-case
|
||||||
|
(setf (memory-object-vector obj)
|
||||||
|
(embeddings-compute raw-content))
|
||||||
|
(error (c)
|
||||||
|
(log-message "INGEST: Embedding deferred: ~a" c))))
|
||||||
id)))
|
id)))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ This replaces the old ~gateway-manager~ skill. The Telegram/Signal platform code
|
|||||||
** Contract
|
** Contract
|
||||||
|
|
||||||
1. (gateway-registry-initialize): populates ~*gateway-registry*~ with
|
1. (gateway-registry-initialize): populates ~*gateway-registry*~ with
|
||||||
~:configured~ status per platform based on env vars.
|
~:configured~ key per platform (boolean, set when linked).
|
||||||
2. (messaging-link platform &key token): stores the token in the vault
|
2. (messaging-link platform &key token): stores the token in the vault
|
||||||
and starts the gateway's polling thread.
|
and starts the gateway's polling thread.
|
||||||
3. (messaging-unlink platform): removes the token and stops the thread.
|
3. (messaging-unlink platform): removes the token and stops the thread.
|
||||||
@@ -142,11 +142,13 @@ This replaces the old ~gateway-manager~ skill. The Telegram/Signal platform code
|
|||||||
(setf (gethash "telegram" *gateway-registry*)
|
(setf (gethash "telegram" *gateway-registry*)
|
||||||
(list :poll-fn #'telegram-poll
|
(list :poll-fn #'telegram-poll
|
||||||
:send-fn #'telegram-send
|
:send-fn #'telegram-send
|
||||||
:default-interval 3))
|
:default-interval 3
|
||||||
|
:configured nil))
|
||||||
(setf (gethash "signal" *gateway-registry*)
|
(setf (gethash "signal" *gateway-registry*)
|
||||||
(list :poll-fn #'signal-poll
|
(list :poll-fn #'signal-poll
|
||||||
:send-fn #'signal-send
|
:send-fn #'signal-send
|
||||||
:default-interval 5)))
|
:default-interval 5
|
||||||
|
:configured nil)))
|
||||||
|
|
||||||
(defun gateway-configured-p (platform)
|
(defun gateway-configured-p (platform)
|
||||||
"Returns T if a platform has a stored token."
|
"Returns T if a platform has a stored token."
|
||||||
@@ -292,9 +294,14 @@ This replaces the old ~gateway-manager~ skill. The Telegram/Signal platform code
|
|||||||
(in-suite messaging-suite)
|
(in-suite messaging-suite)
|
||||||
|
|
||||||
(test test-gateway-registry-initialize
|
(test test-gateway-registry-initialize
|
||||||
"Contract 1: gateway-registry-initialize populates the registry."
|
"Contract 1: gateway-registry-initialize populates the registry with :configured key."
|
||||||
(clrhash passepartout::*gateway-registry*)
|
(clrhash passepartout::*gateway-registry*)
|
||||||
(gateway-registry-initialize)
|
(gateway-registry-initialize)
|
||||||
(is (not (zerop (hash-table-count passepartout::*gateway-registry*))))
|
(is (not (zerop (hash-table-count passepartout::*gateway-registry*))))
|
||||||
(is (getf (gethash "telegram" passepartout::*gateway-registry*) :configured)))
|
(let ((entry (gethash "telegram" passepartout::*gateway-registry*)))
|
||||||
|
(is (getf entry :poll-fn))
|
||||||
|
(is (getf entry :send-fn))
|
||||||
|
(is (getf entry :default-interval))
|
||||||
|
;; :configured key exists and is boolean (nil by default until linked)
|
||||||
|
(is (eq nil (getf entry :configured)))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|||||||
@@ -7,17 +7,20 @@ Event handlers + daemon I/O + main loop.
|
|||||||
|
|
||||||
** Contract
|
** Contract
|
||||||
|
|
||||||
1. (on-key ch): dispatches key presses: Enter triggers send, Backspace
|
1. (on-key ch): dispatches key presses: Enter triggers send (extracts
|
||||||
deletes, arrows scroll chat. Non-printable keys are ignored.
|
input buffer, pushes history, sends to daemon, clears buffer),
|
||||||
|
~/eval <expr>~ evaluates a Lisp expression, ~/focus <proj>~ switches
|
||||||
|
project context, ~/scope <scope>~ changes context scope,
|
||||||
|
~/unfocus~ pops context, Backspace deletes, arrows scroll chat
|
||||||
|
and history. Non-printable keys are ignored.
|
||||||
2. (on-daemon-msg msg): processes inbound daemon messages. Routes
|
2. (on-daemon-msg msg): processes inbound daemon messages. Routes
|
||||||
responses to chat display, routes errors to log.
|
text responses to chat display (:agent), handshake to system
|
||||||
|
messages, routes errors to log via ~log-message~.
|
||||||
3. (send-daemon msg): serializes and sends a message to the daemon
|
3. (send-daemon msg): serializes and sends a message to the daemon
|
||||||
over the framed TCP protocol.
|
over the framed TCP protocol.
|
||||||
4. (handle-return stream): processes the return key: extracts the
|
4. (tui-main): the main loop — connects to daemon, initializes
|
||||||
input buffer, sends to daemon, clears buffer. Handles connection
|
Croatoan windows, optionally starts Swank REPL, runs
|
||||||
loss gracefully (enqueues error to ~*incoming-msgs*~).
|
render/input event loop at ~30fps.
|
||||||
5. (tui-main): the main loop — connects to daemon, initializes
|
|
||||||
Croatoan windows, runs render/input event loop at ~30fps~.
|
|
||||||
|
|
||||||
** Event Handlers
|
** Event Handlers
|
||||||
#+begin_src lisp
|
#+begin_src lisp
|
||||||
@@ -34,21 +37,51 @@ Event handlers + daemon I/O + main loop.
|
|||||||
(push text (st :input-history))
|
(push text (st :input-history))
|
||||||
(setf (st :input-hpos) 0)
|
(setf (st :input-hpos) 0)
|
||||||
(setf (st :scroll-offset) 0)
|
(setf (st :scroll-offset) 0)
|
||||||
(cond
|
(cond
|
||||||
;; /eval command
|
;; /eval command
|
||||||
((and (>= (length text) 6)
|
((and (>= (length text) 6)
|
||||||
(string-equal (subseq text 0 6) "/eval "))
|
(string-equal (subseq text 0 6) "/eval "))
|
||||||
(handler-case
|
(handler-case
|
||||||
(let* ((*read-eval* t)
|
(let* ((*read-eval* t)
|
||||||
(*package* (find-package :passepartout.gateway-tui))
|
(*package* (find-package :passepartout.gateway-tui))
|
||||||
(r (eval (read-from-string (subseq text 6)))))
|
(r (eval (read-from-string (subseq text 6)))))
|
||||||
(add-msg :system (format nil "=> ~s" r)))
|
(add-msg :system (format nil "=> ~s" r)))
|
||||||
(error (c) (add-msg :system (format nil "=> ✗ ~a" c)))))
|
(error (c) (add-msg :system (format nil "=> ✗ ~a" c)))))
|
||||||
;; Normal message
|
;; /focus <project> — set project context
|
||||||
(t
|
((and (>= (length text) 7)
|
||||||
(add-msg :user text)
|
(string-equal (subseq text 0 7) "/focus "))
|
||||||
(send-daemon (list :type :event
|
(let ((project (string-trim '(#\Space) (subseq text 7))))
|
||||||
:payload (list :sensor :user-input :text text)))))
|
(if (and (fboundp 'focus-project) (> (length project) 0))
|
||||||
|
(progn (funcall 'focus-project project nil)
|
||||||
|
(add-msg :system (format nil "Focused on project: ~a" project)))
|
||||||
|
(add-msg :system "Usage: /focus <project-name>"))))
|
||||||
|
;; /scope <scope> — change context scope
|
||||||
|
((and (>= (length text) 7)
|
||||||
|
(string-equal (subseq text 0 7) "/scope "))
|
||||||
|
(let ((scope-str (string-trim '(#\Space) (subseq text 7))))
|
||||||
|
(cond
|
||||||
|
((and (fboundp 'focus-session) (string-equal scope-str "session"))
|
||||||
|
(funcall 'focus-session)
|
||||||
|
(add-msg :system "Scope: session"))
|
||||||
|
((and (fboundp 'focus-project) (string-equal scope-str "project"))
|
||||||
|
(funcall 'focus-project nil nil)
|
||||||
|
(add-msg :system "Scope: project"))
|
||||||
|
((and (fboundp 'focus-memex) (string-equal scope-str "memex"))
|
||||||
|
(funcall 'focus-memex)
|
||||||
|
(add-msg :system "Scope: memex"))
|
||||||
|
(t (add-msg :system "Usage: /scope memex|session|project")))))
|
||||||
|
;; /unfocus — pop context
|
||||||
|
((and (>= (length text) 8)
|
||||||
|
(string-equal (subseq text 0 8) "/unfocus"))
|
||||||
|
(if (fboundp 'unfocus)
|
||||||
|
(progn (funcall 'unfocus)
|
||||||
|
(add-msg :system "Popped context"))
|
||||||
|
(add-msg :system "Context manager not loaded")))
|
||||||
|
;; Normal message
|
||||||
|
(t
|
||||||
|
(add-msg :user text)
|
||||||
|
(send-daemon (list :type :event
|
||||||
|
:payload (list :sensor :user-input :text text)))))
|
||||||
(setf (st :input-buffer) nil)
|
(setf (st :input-buffer) nil)
|
||||||
(setf (st :dirty) (list t t t)))))
|
(setf (st :dirty) (list t t t)))))
|
||||||
;; Backspace
|
;; Backspace
|
||||||
@@ -204,20 +237,149 @@ Event handlers + daemon I/O + main loop.
|
|||||||
(ql:quickload :fiveam :silent t))
|
(ql:quickload :fiveam :silent t))
|
||||||
|
|
||||||
(defpackage :passepartout-tui-tests
|
(defpackage :passepartout-tui-tests
|
||||||
(:use :cl :passepartout)
|
(:use :cl :passepartout :passepartout.gateway-tui)
|
||||||
(:export #:tui-suite))
|
(:export #:tui-suite))
|
||||||
|
|
||||||
(in-package :passepartout-tui-tests)
|
(in-package :passepartout-tui-tests)
|
||||||
|
|
||||||
(fiveam:def-suite tui-suite :description "Verification of the TUI parsing and styling logic")
|
(fiveam:def-suite tui-suite :description "Verification of the TUI model and event handling")
|
||||||
(fiveam:in-suite tui-suite)
|
(fiveam:in-suite tui-suite)
|
||||||
|
|
||||||
(fiveam:test test-tui-connection-drop
|
(fiveam:test test-init-state
|
||||||
"Contract 4: handle-return enqueues error on connection loss."
|
"Contract model.1: init-state returns fresh state plist with required keys."
|
||||||
(let ((passepartout.gateway-tui::*incoming-msgs* nil)
|
(init-state)
|
||||||
(passepartout.gateway-tui::*input-buffer* (make-array 5 :element-type 'character :initial-contents "hello" :fill-pointer 5 :adjustable t))
|
(fiveam:is (eq t (st :running)))
|
||||||
(mock-stream (make-string-output-stream)))
|
(fiveam:is (eq :chat (st :mode)))
|
||||||
(close mock-stream)
|
(fiveam:is (eq nil (st :connected)))
|
||||||
(passepartout.gateway-tui::handle-return mock-stream)
|
(fiveam:is (eq nil (st :stream)))
|
||||||
(fiveam:is (member "ERROR: Connection to daemon lost." passepartout.gateway-tui::*incoming-msgs* :test #'string=))))
|
(fiveam:is (eq nil (st :messages)))
|
||||||
|
(fiveam:is (eq 0 (st :scroll-offset))))
|
||||||
|
|
||||||
|
(fiveam:test test-add-msg
|
||||||
|
"Contract model.2: add-msg appends a message with role, content, and time."
|
||||||
|
(init-state)
|
||||||
|
(add-msg :user "hello")
|
||||||
|
(let* ((msgs (st :messages))
|
||||||
|
(msg (first msgs)))
|
||||||
|
(fiveam:is (eq :user (getf msg :role)))
|
||||||
|
(fiveam:is (string= "hello" (getf msg :content)))
|
||||||
|
(fiveam:is (stringp (getf msg :time)))
|
||||||
|
(fiveam:is (= 5 (length (getf msg :time))))))
|
||||||
|
|
||||||
|
(fiveam:test test-add-msg-dirty-flag
|
||||||
|
"Contract model.2: add-msg sets dirty flags for status and chat."
|
||||||
|
(init-state)
|
||||||
|
(setf (st :dirty) (list nil nil nil))
|
||||||
|
(add-msg :system "boot")
|
||||||
|
(let ((dirty (st :dirty)))
|
||||||
|
(fiveam:is (eq t (first dirty)))
|
||||||
|
(fiveam:is (eq t (second dirty)))
|
||||||
|
(fiveam:is (eq nil (third dirty)))))
|
||||||
|
|
||||||
|
(fiveam:test test-queue-event-roundtrip
|
||||||
|
"Contract model.3: queue-event + drain-queue preserves events in order."
|
||||||
|
(init-state)
|
||||||
|
(queue-event '(:type :key :payload (:ch 13)))
|
||||||
|
(queue-event '(:type :daemon :payload (:text "hi")))
|
||||||
|
(let ((evs (drain-queue)))
|
||||||
|
(fiveam:is (= 2 (length evs)))
|
||||||
|
(fiveam:is (equal '(:type :key :payload (:ch 13)) (first evs)))
|
||||||
|
(fiveam:is (equal '(:type :daemon :payload (:text "hi")) (second evs)))
|
||||||
|
(fiveam:is (null (drain-queue)))))
|
||||||
|
|
||||||
|
(fiveam:test test-on-key-enter-sends-user-message
|
||||||
|
"Contract 1: on-key with Enter extracts input, adds user message, clears buffer."
|
||||||
|
(init-state)
|
||||||
|
;; Simulate typing "test"
|
||||||
|
(dolist (ch '(#\t #\e #\s #\t))
|
||||||
|
(on-key (char-code ch)))
|
||||||
|
(fiveam:is (string= "test" (input-string)))
|
||||||
|
;; Simulate Enter key (char code 13)
|
||||||
|
(on-key 13)
|
||||||
|
;; Input buffer should be cleared
|
||||||
|
(fiveam:is (string= "" (input-string)))
|
||||||
|
;; A user message should be in the message list
|
||||||
|
(let ((msgs (st :messages)))
|
||||||
|
(fiveam:is (>= (length msgs) 1))
|
||||||
|
(let ((last (first msgs)))
|
||||||
|
(fiveam:is (eq :user (getf last :role)))
|
||||||
|
(fiveam:is (string= "test" (getf last :content))))))
|
||||||
|
|
||||||
|
(fiveam:test test-on-key-eval-command
|
||||||
|
"Contract 1: on-key handles /eval command and displays result."
|
||||||
|
(init-state)
|
||||||
|
;; Type "/eval (+ 1 2)"
|
||||||
|
(dolist (ch (coerce "/eval (+ 1 2)" 'list))
|
||||||
|
(on-key (char-code ch)))
|
||||||
|
(on-key 13)
|
||||||
|
(let ((msgs (st :messages)))
|
||||||
|
(fiveam:is (>= (length msgs) 1))
|
||||||
|
(let ((last-msg (first msgs)))
|
||||||
|
(fiveam:is (eq :system (getf last-msg :role)))
|
||||||
|
(fiveam:is (search "=> 3" (getf last-msg :content))))))
|
||||||
|
|
||||||
|
(fiveam:test test-on-key-backspace
|
||||||
|
"Contract 1: on-key with Backspace removes last character from buffer."
|
||||||
|
(init-state)
|
||||||
|
(dolist (ch '(#\a #\b #\c))
|
||||||
|
(on-key (char-code ch)))
|
||||||
|
(fiveam:is (string= "abc" (input-string)))
|
||||||
|
(on-key 127) ; Backspace
|
||||||
|
(fiveam:is (string= "ab" (input-string))))
|
||||||
|
|
||||||
|
(fiveam:test test-disconnect-daemon
|
||||||
|
"Contract 4: disconnect-daemon sets connected to nil and adds disconnect message."
|
||||||
|
(init-state)
|
||||||
|
(setf (st :connected) t
|
||||||
|
(st :stream) (make-string-output-stream))
|
||||||
|
(disconnect-daemon)
|
||||||
|
(fiveam:is (eq nil (st :connected)))
|
||||||
|
(fiveam:is (eq nil (st :stream)))
|
||||||
|
(let ((msgs (st :messages)))
|
||||||
|
(fiveam:is (>= (length msgs) 1))
|
||||||
|
(fiveam:is (search "Disconnected" (getf (first msgs) :content)))))
|
||||||
|
|
||||||
|
(fiveam:test test-on-daemon-msg-handshake
|
||||||
|
"Contract 2: on-daemon-msg handles handshake action."
|
||||||
|
(init-state)
|
||||||
|
(on-daemon-msg '(:type :event :payload (:action :handshake :version "9.9")))
|
||||||
|
(let ((msg (first (st :messages))))
|
||||||
|
(fiveam:is (eq :system (getf msg :role)))
|
||||||
|
(fiveam:is (search "Connected v9.9" (getf msg :content)))))
|
||||||
|
|
||||||
|
(fiveam:test test-on-daemon-msg-text
|
||||||
|
"Contract 2: on-daemon-msg routes text payload to agent message."
|
||||||
|
(init-state)
|
||||||
|
(on-daemon-msg '(:type :event :payload (:text "hello world")))
|
||||||
|
(let ((msg (first (st :messages))))
|
||||||
|
(fiveam:is (eq :agent (getf msg :role)))
|
||||||
|
(fiveam:is (string= "hello world" (getf msg :content)))))
|
||||||
|
|
||||||
|
(fiveam:test test-on-key-focus-command
|
||||||
|
"Contract 1: /focus command parses project name."
|
||||||
|
(init-state)
|
||||||
|
(dolist (ch (coerce "/focus myapp" 'list))
|
||||||
|
(on-key (char-code ch)))
|
||||||
|
(on-key 13)
|
||||||
|
(let ((msg (first (st :messages))))
|
||||||
|
;; When context-manager is loaded, shows "Focused"; otherwise shows "Usage"
|
||||||
|
(fiveam:is (eq :system (getf msg :role)))))
|
||||||
|
|
||||||
|
(fiveam:test test-on-key-scope-command
|
||||||
|
"Contract 1: /scope command with valid argument."
|
||||||
|
(init-state)
|
||||||
|
(dolist (ch (coerce "/scope memex" 'list))
|
||||||
|
(on-key (char-code ch)))
|
||||||
|
(on-key 13)
|
||||||
|
(let ((msg (first (st :messages))))
|
||||||
|
(fiveam:is (eq :system (getf msg :role)))))
|
||||||
|
|
||||||
|
(fiveam:test test-on-key-unfocus-command
|
||||||
|
"Contract 1: /unfocus command dispatches correctly."
|
||||||
|
(init-state)
|
||||||
|
(dolist (ch (coerce "/unfocus" 'list))
|
||||||
|
(on-key (char-code ch)))
|
||||||
|
(on-key 13)
|
||||||
|
(let ((msg (first (st :messages))))
|
||||||
|
(fiveam:is (eq :system (getf msg :role)))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ All state mutation flows through event handlers in the controller.
|
|||||||
(:use :cl :croatoan :passepartout :usocket :bordeaux-threads)
|
(:use :cl :croatoan :passepartout :usocket :bordeaux-threads)
|
||||||
(:export :tui-main :st :add-msg :now :input-string
|
(:export :tui-main :st :add-msg :now :input-string
|
||||||
:queue-event :drain-queue :init-state
|
:queue-event :drain-queue :init-state
|
||||||
:view-status :view-chat :view-input :redraw))
|
:view-status :view-chat :view-input :redraw
|
||||||
|
:on-key :on-daemon-msg :send-daemon
|
||||||
|
:connect-daemon :disconnect-daemon))
|
||||||
(in-package :passepartout.gateway-tui)
|
(in-package :passepartout.gateway-tui)
|
||||||
|
|
||||||
(defvar *state* nil)
|
(defvar *state* nil)
|
||||||
|
|||||||
@@ -10,12 +10,13 @@ State is read via ~(st :key)~ — no mutation here.
|
|||||||
|
|
||||||
1. (view-status win): renders the status bar with connection info,
|
1. (view-status win): renders the status bar with connection info,
|
||||||
version, and timestamp.
|
version, and timestamp.
|
||||||
2. (view-chat win): renders the scrolled chat message list. Messages
|
2. (view-chat win h): renders the scrolled chat message list. Takes
|
||||||
are color-coded: green (user), white (agent), yellow (system).
|
window and available height. Messages are color-coded: green (user),
|
||||||
|
white (agent), yellow (system).
|
||||||
3. (view-input win): renders the input line with cursor and typing
|
3. (view-input win): renders the input line with cursor and typing
|
||||||
indicator.
|
indicator.
|
||||||
4. (redraw scr chat-win status-win input-win): dispatches redraws
|
4. (redraw sw cw ch iw): dispatches redraws based on ~(st :dirty)~
|
||||||
based on ~(st :dirty)~ flags. Minimizes terminal writes.
|
flags (status, chat, input). Minimizes terminal writes.
|
||||||
|
|
||||||
** Status Bar
|
** Status Bar
|
||||||
#+begin_src lisp
|
#+begin_src lisp
|
||||||
|
|||||||
@@ -88,9 +88,16 @@ This replaces the old ~system-embedding-gateway~ with the same logic but renamed
|
|||||||
|
|
||||||
** Object embedding and queuing
|
** Object embedding and queuing
|
||||||
#+begin_src lisp
|
#+begin_src lisp
|
||||||
|
(defvar *embedding-backend* nil
|
||||||
|
"Explicit backend override (nil = use *embedding-provider*).")
|
||||||
|
|
||||||
|
(defun embeddings-compute (text)
|
||||||
|
"Compute an embedding vector for text using the active backend."
|
||||||
|
(embed-object text))
|
||||||
|
|
||||||
(defun embed-object (text)
|
(defun embed-object (text)
|
||||||
"Embed a single text string using the active backend."
|
"Embed a single text string using the active backend."
|
||||||
(let* ((selected (or *embedding-provider* :hashing))
|
(let* ((selected (or *embedding-backend* *embedding-provider* :hashing))
|
||||||
(backend (case selected
|
(backend (case selected
|
||||||
(:local #'embedding-backend-local)
|
(:local #'embedding-backend-local)
|
||||||
(:openai #'embedding-backend-openai)
|
(:openai #'embedding-backend-openai)
|
||||||
@@ -109,15 +116,80 @@ This replaces the old ~system-embedding-gateway~ with the same logic but renamed
|
|||||||
(log-message "EMBEDDING: Queued object"))
|
(log-message "EMBEDDING: Queued object"))
|
||||||
|
|
||||||
(defun embed-all-pending ()
|
(defun embed-all-pending ()
|
||||||
"Drain the embedding queue, batch-process all queued objects."
|
"Drain the embedding queue, store vectors in the store-keyed objects."
|
||||||
(let ((batch (nreverse *embedding-queue*)))
|
(let ((batch (nreverse *embedding-queue*)))
|
||||||
(setf *embedding-queue* nil)
|
(setf *embedding-queue* nil)
|
||||||
(dolist (item batch)
|
(dolist (item batch)
|
||||||
(handler-case
|
(handler-case
|
||||||
(let ((text (if (stringp item) item (format nil "~a" item))))
|
(let ((id (getf item :id))
|
||||||
(embed-object text))
|
(text (getf item :text)))
|
||||||
|
(when (and id text)
|
||||||
|
(let ((vec (embeddings-compute text))
|
||||||
|
(obj (gethash id *memory-store*)))
|
||||||
|
(when (and obj vec (not (listp vec)))
|
||||||
|
(setf (memory-object-vector obj) vec))
|
||||||
|
(log-message "EMBEDDING: Computed vector for ~a (~d dims)" id (length vec)))))
|
||||||
(error (c)
|
(error (c)
|
||||||
(log-message "EMBEDDING: Failed to embed object: ~a" c))))))
|
(log-message "EMBEDDING: Failed to embed object: ~a" c))))))
|
||||||
|
|
||||||
|
;; Apply env var override at load time
|
||||||
|
(let ((provider-env (uiop:getenv "EMBEDDING_PROVIDER")))
|
||||||
|
(when provider-env
|
||||||
|
(let ((kw (intern (string-upcase provider-env) :keyword)))
|
||||||
|
(setf *embedding-provider* kw)
|
||||||
|
(log-message "EMBEDDING: Set provider to ~a from EMBEDDING_PROVIDER env" kw))))
|
||||||
|
|
||||||
(log-message "EMBEDDING: Gateway loaded with provider ~a" *embedding-provider*)
|
(log-message "EMBEDDING: Gateway loaded with provider ~a" *embedding-provider*)
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
|
* Contract
|
||||||
|
|
||||||
|
1. (embeddings-compute text): produces a vector (single-float array) for
|
||||||
|
any text string using the active backend (~*embedding-backend*~ or
|
||||||
|
~*embedding-provider*~).
|
||||||
|
2. (embedding-backend-hashing text): zero-dependency fallback. Returns
|
||||||
|
an 8-element single-float vector deterministically from SHA-256.
|
||||||
|
3. (embed-all-pending): drains ~*embedding-queue*~, computes vectors for
|
||||||
|
all queued objects, and stores them in ~*memory-store*~ entries.
|
||||||
|
|
||||||
|
* Test Suite
|
||||||
|
#+begin_src lisp
|
||||||
|
(eval-when (:compile-toplevel :load-toplevel :execute)
|
||||||
|
(ql:quickload :fiveam :silent t))
|
||||||
|
|
||||||
|
(defpackage :passepartout-embedding-tests
|
||||||
|
(:use :cl :passepartout)
|
||||||
|
(:export #:embedding-suite))
|
||||||
|
|
||||||
|
(in-package :passepartout-embedding-tests)
|
||||||
|
|
||||||
|
(fiveam:def-suite embedding-suite :description "Embedding gateway verification")
|
||||||
|
(fiveam:in-suite embedding-suite)
|
||||||
|
|
||||||
|
(fiveam:test test-embedding-backend-hashing
|
||||||
|
"Contract 2: hashing backend produces 8-element float vector."
|
||||||
|
(let ((vec (embedding-backend-hashing "hello world")))
|
||||||
|
(fiveam:is (arrayp vec))
|
||||||
|
(fiveam:is (= 8 (length vec)))
|
||||||
|
(fiveam:is (every #'numberp (coerce vec 'list)))))
|
||||||
|
|
||||||
|
(fiveam:test test-embedding-backend-hashing-deterministic
|
||||||
|
"Contract 2: same input produces same vector."
|
||||||
|
(let ((v1 (embedding-backend-hashing "test"))
|
||||||
|
(v2 (embedding-backend-hashing "test")))
|
||||||
|
(fiveam:is (equalp v1 v2))))
|
||||||
|
|
||||||
|
(fiveam:test test-embeddings-compute
|
||||||
|
"Contract 1: embeddings-compute returns a float vector."
|
||||||
|
(let ((vec (embeddings-compute "some text")))
|
||||||
|
(fiveam:is (arrayp vec))
|
||||||
|
(fiveam:is (> (length vec) 0))))
|
||||||
|
|
||||||
|
(fiveam:test test-embed-queue-and-drain
|
||||||
|
"Contract 3: embed-all-pending drains queue and stores vectors."
|
||||||
|
(let ((*embedding-queue* nil))
|
||||||
|
(embed-queue-object '(:id "test-obj" :text "sample text"))
|
||||||
|
(fiveam:is (= 1 (length *embedding-queue*)))
|
||||||
|
(embed-all-pending)
|
||||||
|
(fiveam:is (null *embedding-queue*))))
|
||||||
|
#+end_src
|
||||||
|
|||||||
Reference in New Issue
Block a user