FEAT: Implement Matrix Gateway and complete Communication Track

This commit is contained in:
2026-04-11 15:58:15 -04:00
parent 06432d3cdf
commit 0c0a18cb30
9 changed files with 402 additions and 1 deletions

View File

@@ -70,6 +70,8 @@ Interfaces for conversational event handling and UI integration. Source of truth
(or (getf payload :chat-id) (getf proposed-action :chat-id)))
(and (member target '(:signal :SIGNAL))
(or (getf payload :chat-id) (getf proposed-action :chat-id)))
(and (member target '(:matrix :MATRIX))
(or (getf payload :room-id) (getf proposed-action :room-id)))
(and (member target '(:shell :SHELL))
(or (getf payload :cmd) (getf proposed-action :cmd)))
(member target '(:tool :TOOL))))
@@ -98,6 +100,7 @@ The Chat skill acts as the conversational UI. Because the ~org-agent~ kernel eva
(case channel
(:telegram (format nil "- To reply via Telegram: (:type :REQUEST :target :telegram :chat-id \"~a\" :text \"<Response>\")" chat-id))
(:signal (format nil "- To reply via Signal: (:type :REQUEST :target :signal :chat-id \"~a\" :text \"<Response>\")" chat-id))
(:matrix (format nil "- To reply via Matrix: (:type :REQUEST :target :matrix :room-id \"~a\" :text \"<Response>\")" chat-id))
(t "- To reply via Emacs: (:type :REQUEST :target :emacs :action :insert-at-end :buffer \"*org-agent-chat*\" :text \"* <Response>\")"))))
(ask-neuro trimmed-text :system-prompt (concatenate 'string
"ACTUATOR IDENTITY: You are the pure Lisp actuator for the org-agent kernel.

View File

@@ -103,6 +103,8 @@ This function is the secure getter for all system secrets. It prioritizes the Va
(:openrouter "OPENROUTER_API_KEY")
(:telegram "TELEGRAM_BOT_TOKEN")
(:signal "SIGNAL_ACCOUNT_NUMBER")
(:matrix-homeserver "MATRIX_HOMESERVER")
(:matrix-token "MATRIX_ACCESS_TOKEN")
(t nil))))
(when (and env-var (eq type :api-key))
(uiop:getenv env-var))))))

View File

@@ -0,0 +1,188 @@
:PROPERTIES:
:ID: gateway-matrix-skill
:CREATED: [2026-04-11 Sat 17:00]
:END:
#+TITLE: SKILL: Matrix Gateway (Universal Literate Note)
#+STARTUP: content
#+FILETAGS: :gateway:matrix:io:psf:
#+DEPENDS_ON: id:credentials-vault-skill
* Overview
The *Matrix Gateway* provides bi-directional communication via the Matrix Client-Server API. It features an asynchronous polling sensor using the `/sync` endpoint and a registered actuator for outbound `m.room.message` events.
* Phase A: Demand (PRD)
:PROPERTIES:
:STATUS: SIGNED
:END:
** 1. Purpose
Integrate the Org-Agent into the Matrix federation for secure, distributed chat.
** 2. Success Criteria
- [ ] *Inbound:* Messages from Matrix rooms are normalized and injected into the Kernel Bus.
- [ ] *Outbound:* The `:matrix` target correctly routes messages to specific room IDs.
- [ ] *State:* The `since` token is maintained during a session to prevent message loops.
* Phase B: Blueprint (PROTOCOL)
:PROPERTIES:
:STATUS: SIGNED
:END:
** 1. Architectural Intent
Autonomous background polling of the Matrix homeserver. Uses `dexador` for HTTP and `cl-json` for parsing.
** 2. Semantic Interfaces
- `(:sensor :chat-message :channel :matrix ...)`
- `(:type :REQUEST :target :matrix :room-id "..." :text "...")`
* Phase D: Build (Implementation)
** Package Context
#+begin_src lisp :tangle ../src/gateway-matrix.lisp
(in-package :org-agent)
#+end_src
** State: Sync Token
Tracks the last processed event to ensure we only receive new messages.
#+begin_src lisp :tangle ../src/gateway-matrix.lisp
(defvar *matrix-since-token* nil)
#+end_src
** State: Polling Thread
Reference to the background thread responsible for sync requests.
#+begin_src lisp :tangle ../src/gateway-matrix.lisp
(defvar *matrix-polling-thread* nil)
#+end_src
** Credential Retrieval: Homeserver
#+begin_src lisp :tangle ../src/gateway-matrix.lisp
(defun get-matrix-homeserver () (vault-get-secret :matrix-homeserver))
#+end_src
** Credential Retrieval: Token
#+begin_src lisp :tangle ../src/gateway-matrix.lisp
(defun get-matrix-token () (vault-get-secret :matrix-token))
#+end_src
** Actuator: sendMessage
Sends an `m.room.message` to a Matrix room.
#+begin_src lisp :tangle ../src/gateway-matrix.lisp
(defun execute-matrix-action (action context)
"Sends a message via Matrix Client API."
(declare (ignore context))
(let* ((payload (getf action :payload))
(room-id (or (getf payload :room-id) (getf action :room-id)))
(text (or (getf payload :text) (getf action :text)))
(hs (get-matrix-homeserver))
(token (get-matrix-token))
(txn-id (get-universal-time))
(url (format nil "~a/_matrix/client/v3/rooms/~a/send/m.room.message/~a" hs room-id txn-id)))
(when (and hs token room-id text)
(kernel-log "MATRIX: Sending message to ~a..." room-id)
(handler-case
(dex:put url
:headers `(("Authorization" . ,(format nil "Bearer ~a" token))
("Content-Type" . "application/json"))
:content (cl-json:encode-json-to-string
`((msgtype . "m.text") (body . ,text))))
(error (c) (kernel-log "MATRIX ERROR: ~a" c))))))
#+end_src
** Sensor: Sync loop & Injection
Polls the `/sync` endpoint and processes timeline events.
#+begin_src lisp :tangle ../src/gateway-matrix.lisp
(defun matrix-process-sync ()
"Calls Matrix sync and injects new messages."
(let* ((hs (get-matrix-homeserver))
(token (get-matrix-token))
(url (format nil "~a/_matrix/client/v3/sync?timeout=30000~@[&since=~a~]"
hs *matrix-since-token*)))
(when (and hs token)
(handler-case
(let* ((response (dex:get url :headers `(("Authorization" . ,(format nil "Bearer ~a" token)))))
(json (cl-json:decode-json-from-string response))
(next-batch (or (cdr (assoc :next-batch json))
(cdr (assoc :next--batch json))))
(rooms (cdr (assoc :rooms json)))
(joined (cdr (assoc :join rooms))))
(when next-batch
(setf *matrix-since-token* next-batch))
(dolist (room-entry joined)
(let* ((room-id (string-downcase (string (car room-entry))))
(room-data (cdr room-entry))
(timeline (cdr (assoc :timeline room-data)))
(events (cdr (assoc :events timeline))))
(dolist (event events)
(let* ((type (cdr (assoc :type event)))
(content (cdr (assoc :content event)))
(sender (cdr (assoc :sender event)))
(body (cdr (assoc :body content))))
(when (and (string= type "m.room.message") body)
(kernel-log "MATRIX: Received message from ~a in ~a" sender room-id)
(inject-stimulus
(list :type :EVENT
:payload (list :sensor :chat-message
:channel :matrix
:room-id room-id
:sender sender
:text body)))))))))
(error (c) (kernel-log "MATRIX SYNC ERROR: ~a" c))))))
#+end_src
** Start Polling
Initializes the Matrix background thread.
#+begin_src lisp :tangle ../src/gateway-matrix.lisp
(defun start-matrix-gateway ()
"Initializes the Matrix background thread."
(unless (and *matrix-polling-thread* (bt:thread-alive-p *matrix-polling-thread*))
(setf *matrix-polling-thread*
(bt:make-thread
(lambda ()
(loop
(matrix-process-sync)
(sleep 2)))
:name "org-agent-matrix-gateway"))
(kernel-log "MATRIX: Gateway sync active.")))
#+end_src
** Stop Polling
Gracefully terminates the background thread.
#+begin_src lisp :tangle ../src/gateway-matrix.lisp
(defun stop-matrix-gateway ()
(when (and *matrix-polling-thread* (bt:thread-alive-p *matrix-polling-thread*))
(bt:destroy-thread *matrix-polling-thread*)
(setf *matrix-polling-thread* nil)))
#+end_src
** Registration: Actuator
Register the Matrix channel as a physical actuator.
#+begin_src lisp :tangle ../src/gateway-matrix.lisp
(register-actuator :matrix #'execute-matrix-action)
#+end_src
** Registration: Skill
Define the passive skill entry for the gateway.
#+begin_src lisp :tangle ../src/gateway-matrix.lisp
(defskill :skill-gateway-matrix
:priority 150
:trigger (lambda (ctx) (declare (ignore ctx)) nil)
:neuro nil
:symbolic (lambda (action ctx) (declare (ignore ctx)) action))
#+end_src
** Initialization
Trigger the sync loop upon loading.
#+begin_src lisp :tangle ../src/gateway-matrix.lisp
(start-matrix-gateway)
#+end_src