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

7.2 KiB

SKILL: Emacs Bridge Agent (Universal Literate Note)

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)

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)

1. Architectural Intent

Interfaces for TCP I/O and protocol framing. Source of truth is the Harness Communication specification.

2. Semantic Interfaces

(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.")

Phase D: Build (Implementation)

TCP Sensory Layer

(defun handle-emacs-client (stream)
  ;; Logic for parsing length-prefixed Harness Communication messages
  (format nil "Handling client on stream: ~a" stream))

Outbound Actuation

(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)))))))

Registration

(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))

Emacs Bridge Test Suite

;;; 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))))))