diff --git a/docs/superpowers/specs/2026-06-02-openclaw-sidecar-connectors-design.md b/docs/superpowers/specs/2026-06-02-openclaw-sidecar-connectors-design.md new file mode 100644 index 0000000..3b81a13 --- /dev/null +++ b/docs/superpowers/specs/2026-06-02-openclaw-sidecar-connectors-design.md @@ -0,0 +1,396 @@ +# 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.