Phase 1 — dedup + hardening (~9 items): - Remove duplicate *skill-registry* defvar from core-skills - Merge *backend-registry* into *probabilistic-backends*, delete backend-register - Remove inject-stimulus alias, standardize on stimulus-inject - Add pre-eval sandbox (skill-source-scan) blocks restricted symbols before eval - Remove dead plist-get function; remove duplicate json-alist-to-plist export - Fix read-framed-message whitespace DoS (4096-iteration max) - Add *read-eval* nil to dispatcher-approvals-process read-from-string (RCE) - Add test-op to ASDF; update .asd version 0.4.3→0.7.2 Phase 2 — prose + contracts + reorder: - Split ROADMAP: 2623→1089 lines (TODO only), CHANGELOG: 260→1528 lines (full DONE history, 14 versions reverse chron) - Add Contracts + Overview to 6 channel files + embedding-native + programming-standards + symbolic-scope - Reorder 28 .org files: Contract → Test Suite → Implementation (TDD order) - Add 7-phase inline prose to think() in core-reason - Expand USER_MANUAL: 183→461 lines (10 new sections) Phase 3 — decomposition + export organization: - Decompose think() into think-assemble-prompt, think-call-llm, think-parse-response orchestrator - Organize 188 exports into 16 grouped sections by module Phase 4 — budget enforcement + error protocol: - Per-session budget enforcement (SESSION_BUDGET_USD env var, budget-exhausted-p, guard in think-call-llm) - Error condition hierarchy (6 conditions: pipeline-error, llm-error, gate-error, budget-error, protocol-error) - Restarts in loop-process: skip-signal, use-fallback, abort-pipeline
4.3 KiB
4.3 KiB
Channel Discord (channel-discord.org)
Channel Discord
Extracted from gateway-messaging in v0.5.0. Isolated platform — Discord-specific poll and send logic.
Overview
The Discord channel provides bidirectional communication via the Discord REST API
and Gateway WebSocket. Messages received from Discord channels are injected into
the cognitive pipeline as :user-input signals with :source :discord. Outbound
messages route through the actuator registry when the pipeline targets :discord.
The channel uses two functions: discord-poll (inbound sensor, REST polling)
and discord-send (outbound actuator, REST POST). Both retrieve the bot token
from the credentials vault (vault-get-secret :discord). HITL commands are
intercepted before injection so approval flows work identically across all channels.
Contract
- (discord-get-token): returns the Discord bot token from the vault
(via
vault-get-secret :discord), or nil if not configured. - (discord-poll): polls configured channels via GET /channels/{id}/messages,
injects each non-bot message as a
:user-inputstimulus with:source :discord. Handles JSON parse failures and API errors gracefully. HITL commands are intercepted before injection. - (discord-send action context): sends a message via POST /channels/{id}/messages.
Extracts
:channel-idand:textfrom the action plist. Uses bot token authentication. Logs send failures without crashing the pipeline.
Implementation
(in-package :passepartout)
(defun discord-get-token ()
(vault-get-secret :discord))
(defun discord-send (action context)
"Sends a message via Discord REST API."
(declare (ignore context))
(let* ((payload (getf action :payload))
(meta (getf action :meta))
(channel-id (or (getf meta :channel-id) (getf payload :chat-id)))
(text (or (getf payload :text) (getf action :text)))
(token (discord-get-token)))
(when (and token channel-id text)
(handler-case
(dex:post (format nil "https://discord.com/api/v10/channels/~a/messages" channel-id)
:headers '(("Authorization" . ,(format nil "Bot ~a" token))
("Content-Type" . "application/json"))
:content (cl-json:encode-json-to-string
`((content . ,text))))
(error (c) (log-message "DISCORD ERROR: ~a" c))))))
(defun discord-poll ()
"Polls Discord via HTTP GET /channels/{id}/messages. In production,
a WebSocket connection to the Gateway is preferred for real-time events."
(let* ((token (discord-get-token)))
(when token
(handler-case
(dolist (channel '("channel-id-here")) ;; configured channel IDs
(let* ((last-id (getf (gethash "discord" *gateway-configs*) :last-update-id 0))
(url (format nil "https://discord.com/api/v10/channels/~a/messages?after=~a"
channel last-id))
(response (dex:get url :headers
`(("Authorization" . ,(format nil "Bot ~a" token))))))
(let ((messages (ignore-errors
(cdr (assoc :message
(cl-json:decode-json-from-string response))))))
(dolist (msg (and (listp messages) messages))
(let* ((id (cdr (assoc :id msg)))
(content (cdr (assoc :content msg)))
(author (cdr (assoc :author msg)))
(author-id (cdr (assoc :id author)))
(is-bot (cdr (assoc :bot author))))
(when (and id content (not is-bot))
(setf (getf (gethash "discord" *gateway-configs*) :last-update-id) id)
(unless (ignore-errors (hitl-handle-message content :discord))
(stimulus-inject
(list :type :EVENT
:meta (list :source :discord :chat-id channel)
:payload (list :sensor :user-input :text content))))))))))
(error (c) (log-message "DISCORD POLL ERROR: ~a" c))))))
#+end_src