Files
org-agent-contrib/org-skill-emacs-bridge.org

182 lines
7.2 KiB
Org Mode

:PROPERTIES:
:ID: 59ac52fb-2310-4a9d-8e4e-0263af15181c
:CREATED: [2026-03-30 Mon 21:16]
:EDITED: [2026-04-07 Tue 13:42]
:END:
#+TITLE: SKILL: Emacs Bridge Agent (Universal Literate Note)
#+STARTUP: content
#+FILETAGS: :bridge:emacs:io:system:psf:
* Overview
The *Emacs Bridge Agent* is the primary sensory and motor interface to Emacs. It abstracts TCP socket management, allowing the core kernel to interact with buffers as native data structures.
* Phase A: Demand (PRD)
:PROPERTIES:
:STATUS: FROZEN
:END:
** 1. Purpose
Define the transport layer for OpenCortex Communication Protocol (Harness Communication).
** 2. User Needs
- *Isolation:* Kernel remains transport-agnostic.
- *Persistence:* Multi-client server support for simultaneous sessions.
- *Dispatch:* Reliable routing of actions to actuators and sensors to the kernel.
** 3. Success Criteria
*** TODO Socket Listener Initialization
*** TODO Multi-client Connection Handling
*** TODO Harness Communication Message Framing Verification
* Phase B: Blueprint (PROTOCOL)
:PROPERTIES:
:STATUS: SIGNED
:END:
** 1. Architectural Intent
Interfaces for TCP I/O and protocol framing. Source of truth is the Harness Communication specification.
** 2. Semantic Interfaces
#+begin_src lisp
(defun start-emacs-server (&key (port 9105))
"Starts the Harness Communication listener.")
(defun broadcast-to-emacs (action-plist)
"Sends a framed message to all connected clients.")
#+end_src
* Phase D: Build (Implementation)
** TCP Sensory Layer
#+begin_src lisp :tangle ../projects/org-skill-emacs-bridge/src/bridge-logic.lisp
(defun handle-emacs-client (stream)
;; Logic for parsing length-prefixed Harness Communication messages
(format nil "Handling client on stream: ~a" stream))
#+end_src
** Outbound Actuation
#+begin_src lisp :tangle ../projects/org-skill-emacs-bridge/src/bridge-logic.lisp
(defun stream-to-emacs (stream action-plist)
"Streams a chunk of data to a specific Emacs client over Harness Communication using framing."
(let* ((type (or (getf action-plist :type) :request))
(payload (getf action-plist :payload))
;; Ensure Emacs always receives a :payload drawer
(envelope (if (and (getf action-plist :type) payload)
action-plist
(let ((clean-payload (copy-list action-plist)))
(remf clean-payload :type)
(remf clean-payload :id)
(list :type type
:id (or (getf action-plist :id) (get-universal-time))
:payload clean-payload))))
(msg (prin1-to-string envelope))
(len (length msg))
(framed (format nil "~6,'0x~a" len msg)))
(handler-case
(progn
(write-string framed stream)
(finish-output stream))
(error (c)
(kernel-log "BRIDGE - Lost client: ~a" stream)
(opencortex:unregister-emacs-client stream)))))
(defun broadcast-to-emacs (action-plist context)
"Sends a framed message back to the client that sent the stimulus, or all clients if async."
(let ((stream (getf context :reply-stream)))
(if stream
(stream-to-emacs stream action-plist)
(progn
(kernel-log "BRIDGE - Async broadcast to all clients...")
(bt:with-lock-held (opencortex:*clients-lock*)
(dolist (s opencortex:*emacs-clients*)
(stream-to-emacs s action-plist)))))))
#+end_src
* Registration
#+begin_src lisp
(opencortex:register-actuator :emacs #'broadcast-to-emacs)
(defskill :skill-emacs-bridge
:priority 100
:trigger (lambda (context) nil)
:probabilistic (lambda (context) nil)
:deterministic (lambda (action context) action))
#+end_src
* Emacs Bridge Test Suite
#+begin_src elisp :tangle (expand-file-name "tests/org-agent-test.el" (concat (or (getenv "INSTALL_DIR") ".") "/skills"))
;;; opencortex-test.el --- Tests for the opencortex Emacs stub
(require 'ert)
(require 'cl-lib)
(require 'opencortex "/home/amr/.openclaw/workspace/memex/5_projects/opencortex/src/opencortex.el")
(ert-deftest test-opencortex-framing ()
"Verify that opencortex-send correctly frames a plist."
(let ((captured-framed nil))
(cl-letf (((symbol-function 'process-send-string)
(lambda (proc string) (setq captured-framed string)))
((symbol-function 'process-live-p) (lambda (proc) t))
(opencortex--process t))
(opencortex-send '(:type :EVENT :id 1))
(should (string= "000014(:type :EVENT :id 1)" captured-framed)))))
(ert-deftest test-opencortex-parsing ()
"Verify that the filter correctly parses communication protocol framed messages."
(let ((mock-buffer (generate-new-buffer " *opencortex-test*"))
(received-plist nil))
(cl-letf (((symbol-function 'opencortex--handle-message)
(lambda (proc plist) (setq received-plist plist))))
(with-current-buffer mock-buffer
(insert "000014(:type :EVENT :id 1)")
(opencortex--process-buffer mock-buffer)
(should (equal '(:type :EVENT :id 1) received-plist))
(should (= (buffer-size) 0))))))
(ert-deftest test-opencortex-actuator-message ()
"Verify that the :message actuator works."
(let ((opencortex--process nil)
(captured-response nil))
(cl-letf (((symbol-function 'opencortex-send)
(lambda (plist) (setq captured-response plist))))
(opencortex--execute-request nil 101 '(:action :message :text "Hello from Daemon"))
;; Check that we sent a success response back
(should (eq :RESPONSE (plist-get captured-response :type)))
(should (eq :success (plist-get (plist-get captured-response :payload) :status))))))
(ert-deftest test-opencortex-run-command ()
"Verify that opencortex-run-command sends the correct event."
(let ((captured-framed nil))
(cl-letf (((symbol-function 'process-send-string)
(lambda (proc string) (setq captured-framed string)))
((symbol-function 'process-live-p) (lambda (proc) t))
(opencortex--process t))
(opencortex-run-command :test-cmd)
(should (string-match-p ":sensor :user-command" captured-framed))
(should (string-match-p ":command :test-cmd" captured-framed)))))
(ert-deftest test-opencortex-ast-cleaning ()
"Verify that opencortex--clean-element produces a pure plist."
(let* ((org-text "* Hello\nWorld")
(ast (with-temp-buffer
(org-mode)
(insert org-text)
(org-element-parse-buffer)))
(cleaned (opencortex--clean-element ast)))
(should (plist-get cleaned :type))
(should (eq 'org-data (plist-get cleaned :type)))
;; Check that children exist
(should (plist-get (car (plist-get cleaned :contents)) :type))
;; Check that we didn't leak buffer objects
(should-not (plist-get (plist-get cleaned :properties) :buffer))))
(ert-deftest test-opencortex-actuator-eval ()
"Verify that the :eval actuator can execute elisp."
(let ((opencortex--process nil)
(captured-response nil))
(cl-letf (((symbol-function 'opencortex-send)
(lambda (plist) (setq captured-response plist))))
(opencortex--execute-request nil 102 '(:action :eval :code "(+ 1 2)"))
(should (equal "3" (plist-get (plist-get captured-response :payload) :result))))))
#+end_src