Openclaw Interaction Bridge
by @snarflakes
Bridge OpenClaw agent interactions to any external program! The Snarling demo, for example, shows what the agent is doing and lets you approve or reject acti...
clawhub install openclaw-interaction-bridgeπ About This Skill
name: openclaw_interaction_bridge description: "Use the Snarling physical display and A/B button approval system connected to this agent. When loaded, Snarling is already working β state changes (processing, communicating, sleeping) appear on the display automatically. Use request_user_approval for yes/no decisions before destructive or external actions: deleting files, sending emails, publishing packages, deploying to production, transferring funds." metadata: openclaw: emoji: "π₯"
OpenClaw Interaction Bridge π₯
> Agent state on a screen. Approvals on a button. No keyboard required.
This plugin bridges your OpenClaw agent to Snarling β a Raspberry Pi + DisplayHAT Mini companion that shows what the agent is doing and lets you approve or reject actions with physical A/B buttons.
What It Does
State Display
The plugin hooks into OpenClaw events and POSTs state updates to Snarling's display server:
| Agent Activity | Snarling Shows | Trigger |
|---|---|---|
| Using tools | processing | before_tool_call |
| Generating response | communicating | before_agent_reply |
| 30s idle | sleeping | Auto-timeout |
Duplicates are suppressed β only state *changes* are sent. After 30 seconds of no activity, the display automatically goes to sleep.
Physical Approvals β request_user_approval
The plugin registers a request_user_approval tool that routes yes/no decisions to Snarling's physical A/B buttons. Use this tool whenever you need a human decision before proceeding with an action.
#### Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| action | string | Yes | Short verb phrase, max 24 chars (e.g., delete_file, send_email, publish_skill). Shown on the display header line. |
| message | string | Yes | Brief explanation, max 60 chars ideal, 80 chars hard limit. Shown as 2 lines of ~29 chars each on the physical display. Keep it concise β long text gets truncated. (e.g., Delete /tmp/old-logs? Cannot undo.) |
#### When to Use
#### When NOT to Use
#### How It Works
1. You call request_user_approval({ action, message })
2. Plugin creates a TaskFlow and sets it to waiting
3. Snarling displays the request on screen with an A/B button prompt
4. Human presses A (approve) or B (reject)
5. Snarling forwards the decision to the plugin's /approval-callback route
6. Plugin resumes the TaskFlow and returns the result
The tool blocks until a response comes back. Only one approval at a time β if another is in progress, the call returns an error message instead of blocking.
#### Return Values
β
APPROVED β proceed with the actionβ REJECTED β do not proceed; respect the user's decisionβ° Timed out β no response within 30 minutes; treat as rejectedβ οΈ Approval request blocked β another approval is already waiting; finish that one first#### Example
request_user_approval({
action: "delete_file",
message: "Delete old-config.yaml? 90d old, cannot undo."
})
Bad example (too long, gets truncated on display):
request_user_approval({
action: "delete_important_configuration_file", // too long for header
message: "Delete /home/pi/old-config.yaml? This file has not been modified in 90 days and contains important settings." // way too long
})
Setup
Prerequisites
Install
# Clone to your OpenClaw extensions directory
git clone https://github.com/snarflakes/openclaw-interaction-bridge.git \
~/.openclaw/extensions/openclaw-interaction-bridgeInstall dependencies
cd ~/.openclaw/extensions/openclaw-interaction-bridge
npm installRestart OpenClaw
openclaw gateway restart
Custom Targets
To point at something other than the default Snarling ports (e.g., a Tauri app, mobile web view), edit the constants at the top of index.ts:
const SNARLING_URL = "http://localhost:5000/state"; // β your state endpoint
const CALLBACK_BASE_URL = "http://localhost:18789"; // β your callback base URL
For the approval secret (used to authenticate callback requests), set the OPENCLAW_APPROVAL_SECRET environment variable. If not set, a random UUID secret is generated on each startup. The secret must be included in the JSON body of callback requests (not query params β the gateway strips those).
No config file needed yet β when there are multiple adapters, a config-driven system will make sense. For now, editing the source is simpler and more honest.
Architecture
OpenClaw Agent
β (plugin hooks: before_tool_call, before_agent_reply, agent_end)
Interaction Bridge Plugin
β (POST localhost:5000/state) β state updates
β (POST localhost:5000/approval/alert) β approval requests (direct, no middleman)
β (POST localhost:18789/approval-callback) β approval responses
Snarling Display (Python service on port 5000)
No approval_server middleman β the plugin talks directly to Snarling on port 5000. Snarling resolves the approval via its A/B buttons and POSTs the result back to the gateway's /approval-callback route. Wake uses WebSocket RPC (bypasses gateway's requests-in-flight check).
Approval Tracker
The plugin tracks approval lifecycle counts in memory:
| Counter | When it increments |
|---|---|
| requested | Every time request_user_approval is called |
| approved | Callback resolved as approved |
| rejected | Callback resolved as rejected |
| timedOut | Stale lock cleared after 30min timeout |
| errored | Snarling notification POST failed |
Query the stats:
curl -s -X POST http://localhost:18789/approval-callback \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{"action":"stats"}'
Returns: {"stats":{"requested":0,"approved":0,"rejected":0,"timedOut":0,"errored":0}}
Stats are in-memory only β they reset on gateway restart.
Troubleshooting
curl -s http://localhost:5000/state)[approval-callback] entriesopenclaw gateway restart logs for errors; verify npm install completed/approval-callback route with {"action":"stats"} in the bodyInstall from ClawHub
openclaw plugins install clawhub:@snarflakes/openclaw-interaction-bridge
β‘ When to Use
π‘ Examples
request_user_approval({
action: "delete_file",
message: "Delete old-config.yaml? 90d old, cannot undo."
})
Bad example (too long, gets truncated on display):
request_user_approval({
action: "delete_important_configuration_file", // too long for header
message: "Delete /home/pi/old-config.yaml? This file has not been modified in 90 days and contains important settings." // way too long
})
βοΈ Configuration
Prerequisites
Install
# Clone to your OpenClaw extensions directory
git clone https://github.com/snarflakes/openclaw-interaction-bridge.git \
~/.openclaw/extensions/openclaw-interaction-bridgeInstall dependencies
cd ~/.openclaw/extensions/openclaw-interaction-bridge
npm installRestart OpenClaw
openclaw gateway restart
Custom Targets
To point at something other than the default Snarling ports (e.g., a Tauri app, mobile web view), edit the constants at the top of index.ts:
const SNARLING_URL = "http://localhost:5000/state"; // β your state endpoint
const CALLBACK_BASE_URL = "http://localhost:18789"; // β your callback base URL
For the approval secret (used to authenticate callback requests), set the OPENCLAW_APPROVAL_SECRET environment variable. If not set, a random UUID secret is generated on each startup. The secret must be included in the JSON body of callback requests (not query params β the gateway strips those).
No config file needed yet β when there are multiple adapters, a config-driven system will make sense. For now, editing the source is simpler and more honest.
π Tips & Best Practices
curl -s http://localhost:5000/state)[approval-callback] entriesopenclaw gateway restart logs for errors; verify npm install completed/approval-callback route with {"action":"stats"} in the body