#+TITLE - Emacs Actuator Bridge Skill #+AUTHOR - org-agent #+SKILL_NAME - skill-emacs-bridge This skill provides the sensor (TCP Socket) and actuator (OACP Dispatch) for the Emacs interface. It abstracts the I/O layer away from the core `org-agent` kernel. * Sensor & State (TCP Socket Listener) We start a TCP server that listens for incoming connections from `org-agent.el`. #+begin_src lisp (defvar *emacs-server-thread* nil) (defvar *emacs-server-socket* nil) (defvar *active-emacs-clients* nil "List of active Emacs socket streams.") (defvar *emacs-clients-lock* (bt:make-lock "emacs-clients-lock")) (defun handle-emacs-client (stream) "Handle a single OACP connection from Emacs." (bt:with-lock-held (*emacs-clients-lock*) (push stream *active-emacs-clients*)) (unwind-protect (handler-case (loop (let* ((len-buf (make-string 6)) (read-len (read-sequence len-buf stream))) (when (zerop read-len) (return)) (let* ((msg-len (parse-integer len-buf :radix 16)) (msg-buf (make-string msg-len))) (read-sequence msg-buf stream) (let ((plist (read-from-string msg-buf))) (org-agent:kernel-log "BRIDGE: Received message type ~a" (getf plist :type)) ;; PROCESS: Send the message through the 4-stage cognitive loop (org-agent:cognitive-loop plist))))) (error (c) (org-agent:kernel-log "BRIDGE ERROR: ~a" c))) (bt:with-lock-held (*emacs-clients-lock*) (setf *active-emacs-clients* (remove stream *active-emacs-clients*))) (close stream))) (defun start-emacs-server (&key (port 9105)) "Start the OACP listener for Emacs." (setf *emacs-server-socket* (usocket:socket-listen "0.0.0.0" port :reuse-address t)) (setf *emacs-server-thread* (bt:make-thread (lambda () (loop (let ((conn (usocket:socket-accept *emacs-server-socket*))) (bt:make-thread (lambda () (handle-emacs-client (usocket:socket-stream conn))) :name "org-agent-emacs-handler")))) :name "org-agent-emacs-daemon")) (org-agent:kernel-log "BRIDGE: Listening on port ~a" port)) #+end_src * Actuator (Outbound Dispatcher) When the core `cognitive-loop` decides on an action targeting `:emacs`, it delegates to this function. #+begin_src lisp (defun broadcast-to-emacs (action-plist) "Translate an action into OACP framing and send to all connected Emacs clients." (let ((action-msg (org-agent:frame-message (prin1-to-string action-plist)))) (bt:with-lock-held (*emacs-clients-lock*) (dolist (client *active-emacs-clients*) (ignore-errors (write-string action-msg client) (force-output client)))))) #+end_src * Skill Registration Register the skill. We don't use `trigger`, `neuro`, or `symbolic` because this is an I/O skill, not a cognitive skill. We just use the file evaluation to bootstrap the server and register the actuator. #+begin_src lisp ;; Register the actuator with the core Event Bus (org-agent:register-actuator :emacs #'broadcast-to-emacs) ;; Register as a skill so it appears on the dashboard (defskill :skill-emacs-bridge :priority 100 :trigger (lambda (context) nil) :neuro (lambda (context) nil) :symbolic (lambda (action context) action)) ;; Start the socket server when this skill is loaded by the daemon (let* ((env-port (uiop:getenv "ORG_AGENT_DAEMON_PORT")) (port (if env-port (parse-integer env-port :junk-allowed t) 9105))) (unless *emacs-server-thread* (start-emacs-server :port port))) #+end_src