feat: Add initial skills to contrib

This commit is contained in:
2026-04-16 12:00:23 -04:00
parent c5d3d8c5fa
commit a04d8415f3
22 changed files with 2600 additions and 0 deletions

View File

@@ -0,0 +1,157 @@
:PROPERTIES:
:ID: gateway-signal-skill
:CREATED: [2026-04-11 Sat 16:30]
:END:
#+TITLE: SKILL: Signal Gateway (Universal Literate Note)
#+STARTUP: content
#+FILETAGS: :gateway:signal:io:autonomy:
#+DEPENDS_ON: id:credentials-vault-skill
* Overview
The *Signal Gateway* provides bi-directional communication between the Autonomous and the OpenCortex via the `signal-cli` tool. It features a non-blocking polling sensor and a high-integrity actuator for outbound messaging.
* Phase A: Demand (PRD)
:PROPERTIES:
:STATUS: SIGNED
:END:
** 1. Purpose
Enable secure Signal communication for the OpenCortex.
** 2. Success Criteria
- [ ] *Inbound:* Messages received via `signal-cli receive` are injected into the harness Bus.
- [ ] *Outbound:* The `:signal` target correctly routes messages via `signal-cli send`.
- [ ] *Robustness:* Handles JSON output from `signal-cli` and filters system messages.
* Phase B: Blueprint (PROTOCOL)
:PROPERTIES:
:STATUS: SIGNED
:END:
** 1. Architectural Intent
Wraps the `signal-cli` binary. Polling is done in a background thread to prevent blocking the harness.
** 2. Semantic Interfaces
- `(:sensor :chat-message :channel :signal ...)`
- `(:type :REQUEST :target :signal :chat-id "+1..." :text "...")`
* Phase D: Build (Implementation)
** Package Context
#+begin_src lisp
#+end_src
** State: Signal Identity
Retrieves the Signal account number from the secure vault.
#+begin_src lisp
(defun get-signal-account () (vault-get-secret :signal))
#+end_src
** State: Polling Thread
Reference to the background thread responsible for message reception.
#+begin_src lisp
(defvar *signal-polling-thread* nil)
#+end_src
** Actuator: sendMessage
Executes the `signal-cli send` command.
#+begin_src lisp
(defun execute-signal-action (action context)
"Sends a message via signal-cli."
(declare (ignore context))
(let* ((payload (getf action :payload))
(chat-id (or (getf payload :chat-id) (getf action :chat-id)))
(text (or (getf payload :text) (getf action :text)))
(account (get-signal-account)))
(when (and account chat-id text)
(harness-log "SIGNAL: Sending message to ~a..." chat-id)
(handler-case
(uiop:run-program (list "signal-cli" "-u" account "send" "-m" text chat-id)
:output :string :error-output :string)
(error (c) (harness-log "SIGNAL ERROR: ~a" c))))))
#+end_src
** Sensor: receive & Injection
Polls for new messages and injects them into the harness.
#+begin_src lisp
(defun signal-process-updates ()
"Polls for new messages via signal-cli and injects them into the harness."
(let ((account (get-signal-account)))
(when account
(handler-case
(let* ((output (uiop:run-program (list "signal-cli" "-u" account "receive" "--json")
:output :string :error-output :string :ignore-error-status t))
(lines (cl-ppcre:split "\\n" output)))
(dolist (line lines)
(when (and line (> (length line) 0))
(let* ((json (ignore-errors (cl-json:decode-json-from-string line)))
(envelope (cdr (assoc :envelope json)))
(source (cdr (assoc :source envelope)))
(data-message (cdr (assoc :data-message envelope)))
(text (cdr (assoc :message data-message))))
(when (and source text)
(harness-log "SIGNAL: Received message from ~a" source)
(inject-stimulus
(list :type :EVENT
:payload (list :sensor :chat-message
:channel :signal
:chat-id source
:text text))))))))
(error (c) (harness-log "SIGNAL POLL ERROR: ~a" c))))))
#+end_src
** Start Polling
Initializes the Signal background thread.
#+begin_src lisp
(defun start-signal-gateway ()
"Initializes the Signal background thread."
(unless (and *signal-polling-thread* (bt:thread-alive-p *signal-polling-thread*))
(setf *signal-polling-thread*
(bt:make-thread
(lambda ()
(loop
(signal-process-updates)
(sleep 5)))
:name "opencortex-signal-gateway"))
(harness-log "SIGNAL: Gateway polling active.")))
#+end_src
** Stop Polling
Gracefully terminates the background thread.
#+begin_src lisp
(defun stop-signal-gateway ()
(when (and *signal-polling-thread* (bt:thread-alive-p *signal-polling-thread*))
(bt:destroy-thread *signal-polling-thread*)
(setf *signal-polling-thread* nil)))
#+end_src
** Registration: Actuator
Register the Signal channel as a physical actuator.
#+begin_src lisp
(register-actuator :signal #'execute-signal-action)
#+end_src
** Registration: Skill
Define the passive skill entry for the gateway.
#+begin_src lisp
(defskill :skill-gateway-signal
:priority 150
:trigger (lambda (ctx) (declare (ignore ctx)) nil) ;; Passive
:probabilistic nil
:deterministic (lambda (action ctx) (declare (ignore ctx)) action))
#+end_src
** Initialization
Trigger the polling loop upon loading.
#+begin_src lisp
(start-signal-gateway)
#+end_src