# 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-weixin` supports 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 start` verification, and user-vs-bot identity modes: https://www.feishu.cn/content/article/7613711414611463386 ## Scope Included: - A repo-local `openclaw-connector` sidecar 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 `WeixinConnector` and `FeishuConnector` objects registered in `ChannelConnectorRegistry`. - Beaver connector bridge endpoints that accept normalized sidecar inbound events and submit them to `ChannelRuntime.accept_inbound()`. - `ExternalConnectorChannel` runtime 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: ```text 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: ```text 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: ```text 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: ```python 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: ```python 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: ```yaml 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.yml` for local/development sidecar tests. - documentation for attaching `openclaw-connector` to 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`. ```text 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: ```json [ { "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: ```json { "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: ```json { "pairingId": "pair_...", "kind": "weixin", "status": "pending", "qrCode": "weixin://...", "qrImage": "data:image/png;base64,...", "accountId": null, "displayName": null, "error": null, "metadata": {} } ``` Allowed pairing statuses: - `pending` - `qr_ready` - `scanned` - `confirmed` - `connected` - `expired` - `error` - `cancelled` `POST /send` request: ```json { "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: ```text 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: ```json { "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`, and `feishu` - 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 `/send` path. - Start Feishu connector flow using official OpenClaw Lark plugin install path and verify `/feishu start`. ## Rollout Implement in this order: 1. Sidecar HTTP contract with fake provider. 2. Beaver `ExternalConnectorChannel` and bridge endpoint. 3. Weixin connector against fake sidecar client. 4. Feishu connector against fake sidecar client. 5. Frontend connector UI. 6. Real sidecar provider that shells out to OpenClaw/OpenClaw plugin commands. 7. Docker build/compose integration. 8. 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.