12 KiB
OpenClaw Sidecar Connectors Design
Date: 2026-06-02
Goal
Add real Weixin personal-account QR login and Feishu/Lark OpenClaw plugin onboarding to Beaver through a docker-compose predeclared sidecar service. Beaver must not dynamically create containers or require Docker socket access.
This design implements the next connector layer after docs/superpowers/plans/2026-06-02-channel-connectors-foundation.md.
Sources
- Tencent
openclaw-weixinsupports Weixin QR login and persists login credentials after scan confirmation: https://github.com/Tencent/openclaw-weixin - The Weixin ClawBot install flow shown by the user uses
npx -y @tencent-weixin/openclaw-weixin-cli@latest install. - Feishu's OpenClaw article documents the official Lark/Feishu plugin install command
npx -y @larksuite/openclaw-lark install, bot creation/linking,/feishu startverification, and user-vs-bot identity modes: https://www.feishu.cn/content/article/7613711414611463386
Scope
Included:
- A repo-local
openclaw-connectorsidecar service. - A docker-compose service declaration for the sidecar.
- Sidecar HTTP API for health, connector metadata, pairing/install status, logout/remove, outbound send, and inbound event forwarding.
- Beaver
WeixinConnectorandFeishuConnectorobjects registered inChannelConnectorRegistry. - Beaver connector bridge endpoints that accept normalized sidecar inbound events and submit them to
ChannelRuntime.accept_inbound(). ExternalConnectorChannelruntime object for sidecar-backed outbound sends.- Web UI connection wizard for Weixin QR login and Feishu/Lark plugin onboarding.
- Unit tests using fake sidecar clients and bridge events.
Excluded:
- Dynamic Docker container creation from Beaver.
- Docker socket mounts in Beaver.
- Reimplementing Weixin iLink or Feishu OpenClaw protocol inside Beaver.
- Building a generic plugin marketplace.
- Hot-swapping running adapters without backend restart. This phase may create/update connections and require a Beaver app restart for runtime materialization unless explicitly handled by the existing runtime.
- Multi-user enterprise permission governance beyond local connector ownership and bridge token validation.
Architecture
Use one predeclared sidecar for OpenClaw-backed platform connectors:
Beaver backend
-> OpenClawConnector HTTP client
-> openclaw-connector sidecar
-> OpenClaw CLI/runtime
-> @tencent-weixin/openclaw-weixin
-> @larksuite/openclaw-lark
Beaver owns:
- connection state in
ChannelConnectionStore - credential references in
CredentialStore - bridge endpoint authentication
- normalized runtime message admission
- runtime dedupe/session identity
- outbound dispatch into sidecar
/send
The sidecar owns:
- OpenClaw installation/runtime state
- plugin install/update commands
- Weixin QR login and login-state persistence
- Feishu/Lark plugin install, bot creation/linking, and OpenClaw-side verification
- platform receive loops
- sidecar-to-Beaver inbound event delivery
Runtime Flow
Inbound:
Weixin or Feishu/Lark platform event
-> OpenClaw plugin inside sidecar
-> sidecar normalized event
-> POST Beaver /api/channel-connector-bridge/events
-> ChannelRuntime.accept_inbound()
-> MessageBus
-> AgentService
Outbound:
AgentService
-> MessageBus outbound
-> ChannelManager.dispatch_outbound()
-> ExternalConnectorChannel.send()
-> POST sidecar /send
-> OpenClaw plugin send path
-> Weixin or Feishu/Lark platform
ExternalConnectorChannel implements the existing runtime channel protocol:
channel_id: str
kind: str
mode: str
async def start() -> None
async def stop() -> None
async def send(message: OutboundMessage) -> None
It is not a platform protocol adapter. It is a generic HTTP bridge to a sidecar.
Runtime materialization for sidecar-backed connections always emits:
ChannelConfig(
enabled=True,
kind="external_connector",
mode="http",
account_id=spec.account_id,
display_name=spec.display_name,
config={
"platformKind": "weixin",
"connectionId": "conn_...",
"sidecarBaseUrl": "http://openclaw-connector:8787",
},
secrets={"bridgeToken": "..."},
)
The original ChannelConnection.kind remains weixin or feishu; only the runtime transport kind is generic.
Sidecar Deployment
Add a sidecar service that can be enabled in deployment:
services:
openclaw-connector:
build: ./openclaw-connector
restart: unless-stopped
environment:
BEAVER_BRIDGE_BASE_URL: http://app-instance:8080
BEAVER_BRIDGE_TOKEN: ${BEAVER_BRIDGE_TOKEN}
OPENCLAW_HOME: /var/lib/openclaw
volumes:
- openclaw-connector-state:/var/lib/openclaw
For the current create-instance.sh-style deployment, the implementation adds:
docker-compose.openclaw.ymlfor local/development sidecar tests.- documentation for attaching
openclaw-connectorto the same Docker network as the target app instance. - instance environment
OPENCLAW_CONNECTOR_BASE_URL=http://openclaw-connector:8787.
The implementation must not depend on Beaver mounting /var/run/docker.sock.
Sidecar HTTP API
All sidecar requests and responses are JSON. The sidecar listens on port 8787.
GET /health
GET /connectors
POST /pairings
GET /pairings/{pairing_id}
POST /pairings/{pairing_id}/cancel
POST /connections/{connection_id}/logout
POST /send
GET /connectors returns:
[
{
"kind": "weixin",
"displayName": "Weixin",
"authType": "qr",
"capabilities": ["receive_text", "send_text", "receive_media", "direct_messages"]
},
{
"kind": "feishu",
"displayName": "Feishu/Lark",
"authType": "openclaw_plugin",
"capabilities": ["receive_text", "send_text", "receive_media", "groups"]
}
]
POST /pairings request:
{
"kind": "weixin",
"connectionId": "conn_...",
"channelId": "weixin-main",
"displayName": "Weixin Main",
"callbackBaseUrl": "http://app-instance:8080",
"bridgeToken": "..."
}
For Feishu/Lark, kind is feishu and the request may include domain, mode, and optional app credentials when linking an existing bot. If using the OpenClaw official installer to create a bot, the sidecar starts that installer flow and reports QR or action status back to Beaver.
GET /pairings/{pairing_id} response:
{
"pairingId": "pair_...",
"kind": "weixin",
"status": "pending",
"qrCode": "weixin://...",
"qrImage": "data:image/png;base64,...",
"accountId": null,
"displayName": null,
"error": null,
"metadata": {}
}
Allowed pairing statuses:
pendingqr_readyscannedconfirmedconnectedexpirederrorcancelled
POST /send request:
{
"connectionId": "conn_...",
"channelId": "weixin-main",
"kind": "weixin",
"target": {
"peerId": "wx_user_or_chat_id",
"peerType": "dm",
"threadId": null
},
"content": "reply text",
"metadata": {
"contextToken": "optional"
}
}
Beaver Bridge API
Add a backend bridge endpoint for sidecar inbound messages:
POST /api/channel-connector-bridge/events
The sidecar must authenticate every bridge request using a bearer token scoped to the connector service. Beaver rejects missing or invalid bridge tokens.
Bridge event body:
{
"connectionId": "conn_...",
"channelId": "weixin-main",
"kind": "weixin",
"accountId": "weixin:...",
"peerId": "wx_user_or_chat_id",
"peerType": "dm",
"userId": "wx_sender",
"threadId": null,
"messageId": "platform-message-id",
"messageType": "text",
"content": "hello",
"metadata": {
"contextToken": "optional"
}
}
The bridge endpoint constructs ChannelIdentity, then InboundMessage, then calls ChannelRuntime.accept_inbound().
Beaver Connectors
WeixinConnector
Responsibilities:
- discover sidecar health
- start Weixin pairing through sidecar
/pairings - poll sidecar pairing status
- create or update
ChannelConnection - store sidecar connection token or state reference in
CredentialStore - validate by checking sidecar connection status
- materialize runtime config for
ExternalConnectorChannel - revoke/logout by calling sidecar
/connections/{connection_id}/logout
FeishuConnector
Responsibilities:
- discover sidecar health
- start Feishu/Lark OpenClaw plugin install/link flow
- optionally pass appId/appSecret/domain/mode for existing bot linking
- poll installer/pairing status
- create or update
ChannelConnection - validate by sidecar
/pairings/{id}or connector status - materialize runtime config for
ExternalConnectorChannel - revoke/remove plugin connection by calling sidecar logout/remove API
Feishu is sidecar-backed in this design because the user's supplied Feishu article describes the official OpenClaw plugin flow, not only a static bot-credential runtime adapter.
Frontend
Replace the old static Weixin fields with connector-driven UI:
- fetch
GET /api/channel-connectors - show Telegram, Weixin, and Feishu/Lark as connector options
- for Weixin:
- start pairing
- show QR image
- poll status until connected/expired/error
- show connected account and logout
- for Feishu/Lark:
- choose create bot or link existing bot
- collect domain and optional app credentials
- start sidecar pairing/install
- show QR/instructions/status returned by sidecar
- show connected account and logout
The old /api/channels static config editor may remain for advanced runtime config, but connector onboarding should not rely on manual JSON editing or direct token entry for Weixin/Feishu.
Error Handling
- Sidecar unavailable: show connector as
unavailable; do not create a running connection. - OpenClaw install command fails: status
error, with redacted stderr summary. - QR expired: status
expired, user can start a new pairing. - Bridge token invalid: reject with
401, record event without platform secret values. - Unknown connection id in bridge event: reject with
404. - Outbound send failure: mark outbound delivery failed and record connector error.
- Sidecar restart: persisted OpenClaw state should survive through sidecar volume.
Security
- Beaver never logs raw tokens, app secrets, or sidecar connection tokens.
- Bridge token is generated by Beaver and stored behind
credentials_ref. - Sidecar can only call bridge endpoints with its bridge token.
- Sidecar state volume contains OpenClaw login state and must be treated as sensitive.
- Feishu user-identity mode has stronger privacy risk than bot-identity mode; UI must label it clearly if exposed.
Testing
Backend unit tests:
- sidecar client fake for Weixin pairing start/status/logout/send
- sidecar client fake for Feishu pairing start/status/logout/send
ExternalConnectorChannel.send()target mapping- bridge endpoint accepts valid events and rejects invalid token/connection id
- registry lists
telegram,weixin, andfeishu - materialized sidecar connections produce
ChannelConfig(kind="external_connector", mode="http")compatible with runtime factory
Sidecar tests:
- HTTP API shape for health/connectors/pairings/send
- fake OpenClaw provider status transitions
- command runner error redaction
Frontend tests:
- Weixin connector option opens QR modal
- polling reaches connected state
- expired/error states are visible
- Feishu flow starts install/link and shows returned instructions/status
Manual verification:
- Build app and sidecar Docker images.
- Start docker-compose sidecar setup.
- In
terminaltest, open Weixin connector, scan QR, observe connected status. - Send a Weixin text message and verify Beaver receives it.
- Send a Beaver reply and verify sidecar
/sendpath. - Start Feishu connector flow using official OpenClaw Lark plugin install path and verify
/feishu start.
Rollout
Implement in this order:
- Sidecar HTTP contract with fake provider.
- Beaver
ExternalConnectorChanneland bridge endpoint. - Weixin connector against fake sidecar client.
- Feishu connector against fake sidecar client.
- Frontend connector UI.
- Real sidecar provider that shells out to OpenClaw/OpenClaw plugin commands.
- Docker build/compose integration.
- Manual live verification.
The fake provider is test-only. The production sidecar provider must use real OpenClaw plugin commands for Weixin and Feishu/Lark; the fake provider only makes Beaver and frontend tests deterministic while the live provider handles the non-deterministic external login flow.