- Changed all 50 org file :tangle targets from ../lisp/ to ~/.local/share/passepartout/lisp/ (XDG data dir) - Removed 49 generated .lisp files from project lisp/ directory - Removed tests/system-integration-tests.lisp (generated) - Removed lisp/*.fasl (compiled, stale) - Updated core-manifest.org to tangle .asd to XDG root - Remapped quicklisp symlink: local-projects/passepartout → XDG TUI fixes in channel-tui-main.org: - Removed with-raw-terminal (stty raw breaks fd 0 reads in this SBCL) - Use cat subprocess + pipe for keyboard input (via :input :interactive) - Blocking read-char on pipe with with-timeout 0.1s for daemon processing - Key events queued via drain-queue alongside daemon messages - Full dialog key routing (Escape, Up/Down, Enter, filters, Backspace) - SIGWINCH resize handling - Post-handshake backend-size re-query - Daemon version in status bar (was v0.5.0 hardcoded) - Handshake version stored in state, no add-msg - :daemon-version and :size-queried in state plist - view-status uses draw-rect for background - Test section gated with #+passepartout-tests
83 lines
3.6 KiB
Org Mode
83 lines
3.6 KiB
Org Mode
#+TITLE: Channel Signal (channel-signal.org)
|
|
#+AUTHOR: Agent
|
|
#+FILETAGS: :channel:signal:
|
|
#+PROPERTY: header-args:lisp :tangle /home/user/.local/share/passepartout/lisp/channel-signal.lisp
|
|
|
|
* Channel Signal
|
|
|
|
Extracted from gateway-messaging in v0.5.0. Isolated platform — Signal-specific poll and send logic.
|
|
|
|
* Overview
|
|
|
|
The Signal channel provides bidirectional communication via the ~signal-cli~ CLI tool.
|
|
Messages received from Signal contacts are injected into the cognitive pipeline
|
|
as ~:user-input~ signals with ~:source :signal~. Outbound messages route through
|
|
the actuator registry when the pipeline targets ~:signal~.
|
|
|
|
The channel uses two functions: ~signal-poll~ (inbound sensor) and ~signal-send~
|
|
(outbound actuator). Both retrieve the Signal account identifier from the
|
|
credentials vault. HITL commands (~/approve~, ~/deny~) are intercepted before
|
|
injection so approval flows work identically across all channels.
|
|
|
|
** Contract
|
|
|
|
1. (signal-get-account): returns the Signal phone number from the vault
|
|
(via ~vault-get-secret :signal~), or nil if not configured.
|
|
2. (signal-poll): queries ~signal-cli receive --json~ for new messages,
|
|
injects each non-system message as a ~:user-input~ stimulus with
|
|
~:source :signal~. Handles JSON parse failures and network errors
|
|
gracefully (logs and continues). HITL commands are intercepted before
|
|
injection.
|
|
3. (signal-send action context): sends a message via ~signal-cli send~.
|
|
Extracts ~:chat-id~ and ~:text~ from the action plist. Logs send
|
|
failures without crashing the pipeline.
|
|
|
|
* Implementation
|
|
|
|
#+begin_src lisp
|
|
(in-package :passepartout)
|
|
(defun signal-get-account ()
|
|
(vault-get-secret :signal))
|
|
|
|
(defun signal-poll ()
|
|
"Polls Signal for new messages and injects them into the harness."
|
|
(let ((account (signal-get-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)
|
|
(log-message "SIGNAL: Received message from ~a" source)
|
|
(unless (ignore-errors (hitl-handle-message text :signal))
|
|
(stimulus-inject
|
|
(list :type :EVENT
|
|
:meta (list :source :signal :chat-id source)
|
|
:payload (list :sensor :user-input :text text)))))))))
|
|
(error (c) (log-message "SIGNAL POLL ERROR: ~a" c))))))
|
|
|
|
(defun signal-send (action context)
|
|
"Sends a message via Signal."
|
|
(declare (ignore context))
|
|
(let* ((payload (getf action :payload))
|
|
(meta (getf action :meta))
|
|
(chat-id (or (getf meta :chat-id) (getf payload :chat-id) (getf action :chat-id)))
|
|
(text (or (getf payload :text) (getf action :text)))
|
|
(account (signal-get-account)))
|
|
(when (and account chat-id text)
|
|
(handler-case
|
|
(uiop:run-program (list "signal-cli" "-u" account "send" "-m" text chat-id)
|
|
:output :string :error-output :string)
|
|
(error (c) (log-message "SIGNAL ERROR: ~a" c))))))
|
|
#+end_src
|
|
|
|
|
|
#+end_src
|