Files
beaver_project/docs/superpowers/specs/2026-06-02-channel-connectors-and-pairing-design.md

14 KiB

Channel Connectors And Pairing Design

Date: 2026-06-02

Goal

Add a first-class connection layer above Beaver's channel runtime so users can connect messaging platforms through plugin, QR, OAuth, token, or app-credential flows instead of editing static channel JSON by hand.

This design reframes platform channels as two cooperating layers:

ChannelConnector
-> install / auth / QR / OAuth / credential validation / login state
-> ChannelConnectionStore
-> ChannelRuntime
-> ChannelAdapter
-> MessageBus
-> AgentService

The existing ChannelRuntime, MessageBus, ChannelManager, and ChannelAdapter contracts remain the message routing core. The new connector layer owns user-visible setup and connection lifecycle.

Why This Is Required

The current channel design assumes a channel is already configured before the backend starts. That is enough for local development and simple webhook/token channels, but it does not match real platform onboarding:

  • Feishu/Lark now has a Channel SDK pattern that packages bot channel setup, WebSocket or webhook transport, event handling, and replies around an installed app identity.
  • Weixin/OpenClaw-style setup uses a local plugin installer plus QR login and persistent login state.
  • Terminal devices need pairing or device registration; a raw peer_id connect frame is not enough for a real deployment.
  • Even simple token platforms such as Telegram need a UI flow for token entry, validation, status, revoke, and restart.

So Beaver needs a connection lifecycle layer. Adapters should not be responsible for prompting the user, installing packages, storing long-lived credentials, or deciding whether an unknown device is allowed to bind.

Non-Goals

  • Do not replace ChannelRuntime, MessageBus, ChannelManager, or AgentService.
  • Do not make every connector a Node sidecar. Node sidecars are allowed when the official or practical SDK path requires them.
  • Do not implement every channel in this phase.
  • Do not build a plugin marketplace in this phase.
  • Do not store platform secrets in plain channel config when a credential store is available.
  • Do not let external connector code call AgentService directly.

Core Terms

ChannelConnection is the user-visible connection instance. Examples: "Weixin personal account", "Lark workspace bot", "Telegram main bot", "Desk terminal".

ChannelConnector is the setup and lifecycle controller for one platform family. It starts pairing sessions, validates credentials, launches connector processes, handles reconnects, and emits runtime channel config.

ChannelAdapter is the message transport adapter used by ChannelRuntime. It receives normalized inbound messages and sends outbound replies. It does not own onboarding.

ExternalConnectorProcess is an optional local process for platforms whose SDK or login behavior is better isolated outside the Python backend. It talks to Beaver through a narrow control and message protocol.

Data Model

Add a durable connection store under the backend workspace:

@dataclass
class ChannelConnection:
    connection_id: str
    owner_user_id: str | None
    channel_id: str
    kind: str
    mode: str
    display_name: str
    account_id: str
    status: str
    auth_type: str
    credentials_ref: str | None
    connector_ref: str | None
    pairing_session_id: str | None
    runtime_config: dict[str, Any]
    capabilities: list[str]
    created_at: str
    updated_at: str
    last_seen_at: str | None
    last_error: str | None

status values:

  • draft: setup has started but no credentials are usable.
  • pairing: waiting for QR scan, OAuth callback, device approval, or token validation.
  • connected: credentials are valid and the runtime channel can start.
  • running: the runtime adapter or external connector is active.
  • degraded: partially working, for example inbound works but media upload failed.
  • error: connection cannot start or authenticate.
  • revoked: user or platform revoked the connection.

Credential material should live behind credentials_ref, not inline in ChannelConnection. For the first local implementation, the reference may point to an encrypted file or a restricted JSON store. The interface should still look like a credential vault so AuthZ or a real secret backend can replace it later.

Connector Contract

Every connector implements a setup contract:

class ChannelConnector(Protocol):
    kind: str

    async def start_pairing(request: StartPairingRequest) -> PairingSession
    async def complete_pairing(event: PairingEvent) -> ChannelConnection
    async def validate(connection_id: str) -> ValidationResult
    async def materialize_runtime(connection_id: str) -> ChannelRuntimeSpec
    async def revoke(connection_id: str) -> None

materialize_runtime() returns the adapter-ready config:

@dataclass
class ChannelRuntimeSpec:
    channel_id: str
    kind: str
    mode: str
    account_id: str
    display_name: str
    config: dict[str, Any]
    secrets_ref: str | None
    external_endpoint: str | None

The runtime may still internally use ChannelConfig, but the source of truth becomes ChannelConnectionStore, not only static BeaverConfig.channels.

Control APIs

Add backend APIs for the connection UI:

GET    /api/channel-connectors
GET    /api/channel-connections
POST   /api/channel-connections
GET    /api/channel-connections/{connection_id}
POST   /api/channel-connections/{connection_id}/pairing/start
POST   /api/channel-connections/{connection_id}/pairing/complete
POST   /api/channel-connections/{connection_id}/validate
POST   /api/channel-connections/{connection_id}/start
POST   /api/channel-connections/{connection_id}/stop
POST   /api/channel-connections/{connection_id}/revoke
GET    /api/channel-connections/{connection_id}/events

The existing /api/channels status endpoint can keep reporting runtime adapter status, but the UI should prefer /api/channel-connections for setup state.

UI Flow

The status page becomes a channel connection page:

Add Channel
-> choose platform
-> connector-specific setup form
-> QR/OAuth/token/app credential validation
-> connection status
-> start runtime channel
-> test message or platform health check

The UI must distinguish:

  • setup state: pairing, credential validation, revoked.
  • runtime state: adapter running, disconnected, outbound failed.
  • platform state: QR expired, app not installed, permission missing, token invalid.

This avoids the current problem where all failures collapse into adapter startup errors.

External Connector Process

Some channels should run through an external process:

ExternalConnectorProcess
-> Beaver connector control API
-> local Unix/TCP/WebSocket bridge
-> ChannelRuntime external adapter

The external process must not receive permanent backend admin credentials through QR codes or copied commands. It should receive a short-lived pairing token with a narrow scope:

scope: channel:pair
kind: weixin
expires_in: 10 minutes
one_time: true

After pairing, Beaver stores the resulting connection credentials and gives the connector a renewable connection token scoped to that connection only.

Per-Channel Assessment

Feishu / Lark

Feishu/Lark should be a first-class connector, not only a static adapter.

Recommended first implementation:

  • connector kind: feishu
  • setup fields: domain, app id, app secret, connection mode.
  • default mode: WebSocket long connection.
  • optional mode: webhook.
  • runtime adapter: may be Python if coverage is sufficient, or an external Node connector when using official Channel SDK behavior.

Required setup checks:

  • app credentials are present.
  • bot/event permissions are configured.
  • event subscription mode is valid.
  • bot identity can be resolved.
  • a test direct message or event subscription health check can run when available.

The connector should expose both "manual app credential setup" and future "install from app template" paths. The manual path is enough for the first Beaver release.

Weixin

Weixin should use an external connector process.

Recommended first implementation:

  • connector kind: weixin
  • setup mode: local plugin command plus QR login.
  • external process: required.
  • runtime adapter: external bridge adapter that receives normalized events from the connector.

Required setup checks:

  • local connector installed.
  • connector version is compatible with Beaver.
  • QR session is pending, scanned, confirmed, expired, or failed.
  • login state is stored behind credentials_ref.
  • connector heartbeat is visible.

Group delivery remains best-effort. The connector must surface group capability separately from direct message capability.

Telegram

Telegram can be implemented as an internal connector plus internal adapter.

Recommended first implementation:

  • setup mode: bot token entry.
  • validation: call Telegram getMe.
  • runtime mode: polling by default, webhook optional.
  • no external process required.

The UI still treats it as a connector so users can add, validate, revoke, and restart it without editing JSON.

QQBot

QQBot should start as an internal connector with official gateway credentials.

Recommended first implementation:

  • setup fields: app id, client secret, intents or permission hints.
  • runtime mode: WebSocket gateway.
  • validation: token exchange or gateway auth dry run when available.

If SDK/runtime behavior later becomes easier outside Python, this connector can move to an external process without changing the runtime message contract.

Terminal

Terminal should move from raw peer_id to pairing.

Recommended first implementation:

  • UI creates a terminal pairing session.
  • Beaver displays a command or QR/setup code.
  • device connects with one-time pairing token.
  • Beaver binds a stable device identity to a ChannelConnection.
  • subsequent WebSocket connect frames authenticate as the bound device.

The message protocol can keep connect, connected, message, ack, and assistant message, but production connections must include an authenticated device token.

Message Flow After Pairing

Once a connection is paired, the message path stays unchanged:

platform or device
-> connector transport
-> ChannelAdapter
-> ChannelRuntime.accept_inbound()
-> MessageBus.inbound
-> AgentService.handle_inbound_message()
-> MessageBus.outbound
-> ChannelManager.dispatch_outbound()
-> ChannelAdapter.send()
-> connector transport
-> platform or device

This is intentionally conservative. Pairing changes how a channel becomes trusted and running; it does not change the agent loop.

Access Control

Connection setup requires a Beaver user or backend owner identity. The connector layer decides who may create, view, revoke, or start a connection.

Inbound platform messages still use adapter-level policy:

  • open: accept platform scope.
  • allowlist: accept only known users/groups.
  • disabled: ignore that scope.

The important change is that allowlists belong to the connection settings, not ad hoc adapter config only.

Error Handling

Pairing errors:

  • expired pairing token.
  • QR not scanned before timeout.
  • OAuth callback state mismatch.
  • platform permission missing.
  • credentials validation failed.

Runtime errors:

  • adapter startup failed.
  • connector process unavailable.
  • heartbeat missed.
  • inbound normalization failed.
  • outbound delivery failed.

Each event should be recorded against connection_id and, when available, channel_id and session_id.

Security Requirements

  • Pairing tokens are short-lived, one-time, and scoped to one connector kind.
  • QR codes never embed permanent backend credentials.
  • External connector processes do not receive broad backend admin tokens.
  • Revoking a connection invalidates connector tokens and stops the runtime channel.
  • Stored platform credentials are referenced by credentials_ref.
  • Event logs must not include raw secrets, tokens, QR payloads, or full platform credential responses.

Relationship To Existing Channel Specs

The terminal WebSocket spec remains valid as a development transport spec, but production terminal setup must add pairing.

The chat platform adapter spec remains valid as a runtime adapter spec, but these statements should be revised before implementation:

  • "Do not introduce a Node sidecar as the default channel architecture" should become "Use internal adapters by default, but allow external connector processes where platform SDK or login state requires them."
  • "Pairing is out of scope for this phase" should become "Pairing is owned by the connector layer; adapters assume a materialized connection."
  • Static BeaverConfig.channels should become a development override and backward-compatible import path, not the only source of runtime channels.

Rollout

Implement in this order:

  1. ChannelConnectionStore, connector registry, and connection status APIs.
  2. Telegram connector as the simplest token-based setup path.
  3. Terminal pairing to remove raw unauthenticated peer_id usage.
  4. Feishu/Lark connector with WebSocket long-connection mode and credential validation.
  5. Weixin external connector bridge with QR pairing.
  6. QQBot connector after the common credential and gateway patterns are stable.

This order proves the common connector lifecycle with a low-risk token channel before adding QR and external process complexity.

Testing

Add unit tests for:

  • connection store create/update/revoke.
  • pairing token expiry and one-time use.
  • connector registry dispatch by kind.
  • materializing runtime specs from connections.
  • secret redaction in events.
  • adapter runtime still receiving normalized InboundMessage.

Add integration-style tests with fake connectors for:

  • successful token setup.
  • QR expired and QR completed.
  • external connector heartbeat loss.
  • revoke stops runtime dispatch.

Live platform tests remain manual or gated behind explicit environment variables.

Acceptance Criteria

  • A user can add a channel connection without editing backend JSON.
  • Beaver can show setup state separately from runtime adapter state.
  • Telegram can validate a bot token and materialize a runtime channel.
  • Terminal can bind through a one-time pairing flow.
  • Feishu/Lark design allows official SDK or Node connector use when needed.
  • Weixin design requires an external connector and QR login state.
  • Existing channel runtime message flow remains bus-first and adapter-mediated.