FEAT: Implement Signal Gateway and update Chat Agent
This commit is contained in:
@@ -68,6 +68,8 @@ Interfaces for conversational event handling and UI integration. Source of truth
|
||||
(member action '(:insert-at-end :INSERT-AT-END)))
|
||||
(and (member target '(:telegram :TELEGRAM))
|
||||
(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 '(:shell :SHELL))
|
||||
(or (getf payload :cmd) (getf proposed-action :cmd)))
|
||||
(member target '(:tool :TOOL))))
|
||||
@@ -95,6 +97,7 @@ The Chat skill acts as the conversational UI. Because the ~org-agent~ kernel eva
|
||||
(reply-instruction
|
||||
(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))
|
||||
(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.
|
||||
|
||||
155
skills/org-skill-gateway-signal.org
Normal file
155
skills/org-skill-gateway-signal.org
Normal file
@@ -0,0 +1,155 @@
|
||||
: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:psf:
|
||||
#+DEPENDS_ON: id:credentials-vault-skill
|
||||
|
||||
* Overview
|
||||
The *Signal Gateway* provides bi-directional communication between the Sovereign and the Org-Agent 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 Org-Agent.
|
||||
|
||||
** 2. Success Criteria
|
||||
- [ ] *Inbound:* Messages received via `signal-cli receive` are injected into the Kernel 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 kernel.
|
||||
|
||||
** 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 :tangle ../src/gateway-signal.lisp
|
||||
(in-package :org-agent)
|
||||
#+end_src
|
||||
|
||||
** State: Signal Identity
|
||||
The primary account number used for communication.
|
||||
|
||||
#+begin_src lisp :tangle ../src/gateway-signal.lisp
|
||||
(defvar *signal-account* "+13322690326")
|
||||
#+end_src
|
||||
|
||||
** State: Polling Thread
|
||||
Reference to the background thread responsible for message reception.
|
||||
|
||||
#+begin_src lisp :tangle ../src/gateway-signal.lisp
|
||||
(defvar *signal-polling-thread* nil)
|
||||
#+end_src
|
||||
|
||||
** Actuator: sendMessage
|
||||
Executes the `signal-cli send` command.
|
||||
|
||||
#+begin_src lisp :tangle ../src/gateway-signal.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))))
|
||||
(when (and chat-id text)
|
||||
(kernel-log "SIGNAL: Sending message to ~a..." chat-id)
|
||||
(handler-case
|
||||
(uiop:run-program (list "signal-cli" "-u" *signal-account* "send" "-m" text chat-id)
|
||||
:output :string :error-output :string)
|
||||
(error (c) (kernel-log "SIGNAL ERROR: ~a" c))))))
|
||||
#+end_src
|
||||
|
||||
** Sensor: receive & Injection
|
||||
Polls for new messages and injects them into the kernel.
|
||||
|
||||
#+begin_src lisp :tangle ../src/gateway-signal.lisp
|
||||
(defun signal-process-updates ()
|
||||
"Polls for new messages via signal-cli and injects them into the kernel."
|
||||
(handler-case
|
||||
(let* ((output (uiop:run-program (list "signal-cli" "-u" *signal-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)
|
||||
(kernel-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) (kernel-log "SIGNAL POLL ERROR: ~a" c))))
|
||||
#+end_src
|
||||
|
||||
** Start Polling
|
||||
Initializes the Signal background thread.
|
||||
|
||||
#+begin_src lisp :tangle ../src/gateway-signal.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 "org-agent-signal-gateway"))
|
||||
(kernel-log "SIGNAL: Gateway polling active.")))
|
||||
#+end_src
|
||||
|
||||
** Stop Polling
|
||||
Gracefully terminates the background thread.
|
||||
|
||||
#+begin_src lisp :tangle ../src/gateway-signal.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 :tangle ../src/gateway-signal.lisp
|
||||
(register-actuator :signal #'execute-signal-action)
|
||||
#+end_src
|
||||
|
||||
** Registration: Skill
|
||||
Define the passive skill entry for the gateway.
|
||||
|
||||
#+begin_src lisp :tangle ../src/gateway-signal.lisp
|
||||
(defskill :skill-gateway-signal
|
||||
:priority 150
|
||||
:trigger (lambda (ctx) (declare (ignore ctx)) nil) ;; Passive
|
||||
:neuro nil
|
||||
:symbolic (lambda (action ctx) (declare (ignore ctx)) action))
|
||||
#+end_src
|
||||
|
||||
** Initialization
|
||||
Trigger the polling loop upon loading.
|
||||
|
||||
#+begin_src lisp :tangle ../src/gateway-signal.lisp
|
||||
(start-signal-gateway)
|
||||
#+end_src
|
||||
Reference in New Issue
Block a user