feat: implement channel runtime connectors
This commit is contained in:
1105
docs/superpowers/plans/2026-06-01-terminal-websocket-channel.md
Normal file
1105
docs/superpowers/plans/2026-06-01-terminal-websocket-channel.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -6,6 +6,8 @@
|
||||
|
||||
**Architecture:** Add a connector layer under `beaver/interfaces/channels/connections/` while keeping `ChannelRuntime`, `MessageBus`, and existing adapters as the message path. A `ChannelConnectionStore` persists setup state, a small credential vault stores secrets by reference, and `ChannelConnectorRegistry` materializes enabled connections into `ChannelConfig` objects during app startup. The first concrete connector is Telegram because token validation and runtime materialization are simple and testable with fake clients.
|
||||
|
||||
**Weixin Follow-Up Constraint:** Weixin personal-account support is not implemented in this foundation slice. The follow-up Weixin plan must use a docker-compose predeclared sidecar service and Beaver must only call the existing connector HTTP API; Beaver must not dynamically create containers or require Docker socket access. Because the sidecar owns Weixin protocol, QR login, receive, and send behavior, Beaver should expose it to `ChannelRuntime` as an `ExternalConnectorChannel`, not as a protocol-level `WeixinAdapter`.
|
||||
|
||||
**Tech Stack:** Python dataclasses, FastAPI, Pydantic v2, local JSON stores, pytest, existing Beaver channel runtime.
|
||||
|
||||
---
|
||||
@ -28,7 +30,7 @@ Excluded from this plan:
|
||||
|
||||
- Terminal authenticated pairing.
|
||||
- Feishu/Lark official SDK integration.
|
||||
- Weixin external connector process.
|
||||
- Weixin docker-compose sidecar pairing and bridge implementation. The later Weixin plan must use a predeclared sidecar service plus Beaver HTTP bridge endpoints, not local host dependencies, dynamic container creation, or a Beaver-owned Weixin protocol adapter.
|
||||
- QQBot connector.
|
||||
- Frontend connection wizard.
|
||||
- Hot starting/stopping adapters without backend restart.
|
||||
@ -1739,9 +1741,19 @@ rg -n "token-1|token-2|bad-token|secret-token" tests/unit beaver || true
|
||||
|
||||
Expected: test fixture strings only appear in test files. They must not appear in implementation files or generated event log code.
|
||||
|
||||
- [ ] **Step 4: Update adapter spec wording if still contradictory**
|
||||
- [ ] **Step 4: Update connector and adapter spec wording if still contradictory**
|
||||
|
||||
If `docs/superpowers/specs/2026-06-02-chat-platform-channel-adapters-design.md` still says pairing is out of scope and Node sidecars are disallowed, change only the Non-Goals and Access Control text:
|
||||
If `docs/superpowers/specs/2026-06-02-channel-connectors-and-pairing-design.md` still says Weixin uses a local plugin installer, dynamically launched connector process, or `ChannelRuntime external adapter`, change only the Weixin/external-process wording to this architecture:
|
||||
|
||||
```markdown
|
||||
Weixin personal-account support uses a docker-compose predeclared sidecar connector. Beaver calls the sidecar's existing HTTP API and must not dynamically create containers or require Docker socket access.
|
||||
```
|
||||
|
||||
```markdown
|
||||
For Weixin, the sidecar owns the platform protocol, QR login, inbound receive loop, outbound send, and login-state persistence. Beaver exposes it to the runtime through an `ExternalConnectorChannel`: inbound sidecar webhooks call Beaver's connector bridge endpoint, which submits normalized messages to `ChannelRuntime.accept_inbound()`, while outbound runtime messages call the sidecar `/send` API.
|
||||
```
|
||||
|
||||
If `docs/superpowers/specs/2026-06-02-chat-platform-channel-adapters-design.md` still describes `WeixinAdapter` as the Beaver-owned protocol adapter, change only the Weixin adapter scope and access-control text:
|
||||
|
||||
```markdown
|
||||
- Use internal adapters by default, but allow external connector processes where platform SDK or login state requires them.
|
||||
@ -1751,17 +1763,40 @@ If `docs/superpowers/specs/2026-06-02-chat-platform-channel-adapters-design.md`
|
||||
Pairing is owned by the connector layer. Platform adapters assume a materialized `ChannelConnection` and adapter-ready runtime config.
|
||||
```
|
||||
|
||||
```markdown
|
||||
For Weixin personal-account support, the runtime channel is an `ExternalConnectorChannel`, not a Beaver-owned `WeixinAdapter`. The docker-compose sidecar is the Weixin protocol adapter; Beaver only owns connector setup state, bridge API validation, message normalization boundaries, runtime dedupe, and outbound HTTP calls to the sidecar.
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Commit spec alignment if changed**
|
||||
|
||||
If Step 4 changed docs:
|
||||
|
||||
```bash
|
||||
git add docs/superpowers/specs/2026-06-02-chat-platform-channel-adapters-design.md
|
||||
git commit -m "docs: align channel adapter spec with connector layer"
|
||||
git add \
|
||||
docs/superpowers/specs/2026-06-02-channel-connectors-and-pairing-design.md \
|
||||
docs/superpowers/specs/2026-06-02-chat-platform-channel-adapters-design.md
|
||||
git commit -m "docs: align channel specs with external connector channels"
|
||||
```
|
||||
|
||||
If Step 4 made no change, do not create an empty commit.
|
||||
|
||||
- [ ] **Step 6: Summarize remaining rollout**
|
||||
|
||||
Record in the final implementation response that this first plan does not implement Terminal pairing, Feishu/Lark connector, Weixin external connector, QQBot connector, frontend wizard, or hot adapter restart. Those are separate plans.
|
||||
Record in the final implementation response that this first plan does not implement Terminal pairing, Feishu/Lark connector, Weixin docker-compose sidecar pairing/bridge, QQBot connector, frontend wizard, or hot adapter restart. Those are separate plans.
|
||||
|
||||
For Weixin specifically, record the agreed follow-up architecture:
|
||||
|
||||
```text
|
||||
Weixin sidecar connector
|
||||
-> Beaver connector bridge endpoint
|
||||
-> ChannelRuntime.accept_inbound()
|
||||
-> MessageBus
|
||||
-> AgentService
|
||||
|
||||
AgentService
|
||||
-> MessageBus outbound
|
||||
-> ExternalConnectorChannel.send()
|
||||
-> Weixin sidecar connector /send
|
||||
```
|
||||
|
||||
Do not describe the follow-up path as `sidecar -> WeixinAdapter -> ChannelRuntime`. The sidecar is the Weixin protocol adapter; Beaver's runtime object should be named `ExternalConnectorChannel` or an equivalently generic connector-bridge channel.
|
||||
|
||||
1515
docs/superpowers/plans/2026-06-02-chat-platform-channel-adapters.md
Normal file
1515
docs/superpowers/plans/2026-06-02-chat-platform-channel-adapters.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -13,19 +13,19 @@ ChannelConnector
|
||||
-> install / auth / QR / OAuth / credential validation / login state
|
||||
-> ChannelConnectionStore
|
||||
-> ChannelRuntime
|
||||
-> ChannelAdapter
|
||||
-> ChannelAdapter or ExternalConnectorChannel
|
||||
-> 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.
|
||||
The existing `ChannelRuntime`, `MessageBus`, `ChannelManager`, and `ChannelAdapter` contracts remain the message routing core. The new connector layer owns user-visible setup and connection lifecycle. For platforms backed by predeclared sidecar services, Beaver should expose the sidecar to the runtime as an `ExternalConnectorChannel` rather than a Beaver-owned platform protocol adapter.
|
||||
|
||||
## 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.
|
||||
- Weixin personal-account setup uses a docker-compose predeclared sidecar connector 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.
|
||||
|
||||
@ -44,11 +44,13 @@ So Beaver needs a connection lifecycle layer. Adapters should not be responsible
|
||||
|
||||
`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.
|
||||
`ChannelConnector` is the setup and lifecycle controller for one platform family. It starts pairing sessions, validates credentials, checks preconfigured connector endpoints when needed, 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.
|
||||
`ExternalConnectorChannel` is the runtime channel object used when a platform protocol lives outside the Python backend. It implements the same `start()`, `stop()`, and `send()` contract as an adapter, but its `send()` method calls an external connector HTTP API and inbound messages enter Beaver through a connector bridge endpoint.
|
||||
|
||||
`ExternalConnectorProcess` is an optional preconfigured service for platforms whose SDK or login behavior is better isolated outside the Python backend. For Weixin, this process is a docker-compose predeclared sidecar service. Beaver must not dynamically create containers or require Docker socket access.
|
||||
|
||||
## Data Model
|
||||
|
||||
@ -171,7 +173,7 @@ Some channels should run through an external process:
|
||||
ExternalConnectorProcess
|
||||
-> Beaver connector control API
|
||||
-> local Unix/TCP/WebSocket bridge
|
||||
-> ChannelRuntime external adapter
|
||||
-> ChannelRuntime ExternalConnectorChannel
|
||||
```
|
||||
|
||||
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:
|
||||
@ -183,7 +185,7 @@ 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.
|
||||
After pairing, Beaver stores the resulting connection credentials and gives the connector a renewable connection token scoped to that connection only. For docker-compose sidecars, that token is passed through the connector HTTP API or service configuration agreed for that sidecar; Beaver does not create or restart the sidecar container.
|
||||
|
||||
## Per-Channel Assessment
|
||||
|
||||
@ -211,23 +213,41 @@ The connector should expose both "manual app credential setup" and future "insta
|
||||
|
||||
### Weixin
|
||||
|
||||
Weixin should use an external connector process.
|
||||
Weixin should use a docker-compose predeclared sidecar connector.
|
||||
|
||||
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.
|
||||
- setup mode: Beaver calls the sidecar HTTP API to start QR login and poll pairing state.
|
||||
- external process: required, predeclared in docker-compose, and never dynamically created by Beaver.
|
||||
- runtime channel: `ExternalConnectorChannel`.
|
||||
|
||||
Required setup checks:
|
||||
|
||||
- local connector installed.
|
||||
- sidecar base URL is configured.
|
||||
- sidecar health endpoint responds.
|
||||
- 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.
|
||||
|
||||
The sidecar owns Weixin protocol handling, QR login, inbound receive, outbound send, and login-state persistence. Beaver owns connector setup state, bridge API validation, message normalization boundaries, runtime dedupe, and outbound HTTP calls to the sidecar `/send` API.
|
||||
|
||||
The agreed runtime flow is:
|
||||
|
||||
```text
|
||||
Weixin sidecar connector
|
||||
-> Beaver connector bridge endpoint
|
||||
-> ChannelRuntime.accept_inbound()
|
||||
-> MessageBus
|
||||
-> AgentService
|
||||
|
||||
AgentService
|
||||
-> MessageBus outbound
|
||||
-> ExternalConnectorChannel.send()
|
||||
-> Weixin sidecar connector /send
|
||||
```
|
||||
|
||||
Group delivery remains best-effort. The connector must surface group capability separately from direct message capability.
|
||||
|
||||
### Telegram
|
||||
|
||||
@ -0,0 +1,307 @@
|
||||
# Chat Platform Channel Adapters Design
|
||||
|
||||
Date: 2026-06-02
|
||||
|
||||
## Goal
|
||||
|
||||
Add first-class Beaver channel adapters for four messaging platforms:
|
||||
|
||||
- `FeishuAdapter`
|
||||
- `QQBotAdapter`
|
||||
- `TelegramAdapter`
|
||||
- `ExternalConnectorChannel` for Weixin personal-account sidecars
|
||||
|
||||
Each runtime channel must plug into the existing `ChannelRuntime`, normalize inbound platform messages into `InboundMessage` with `ChannelIdentity`, and deliver `OutboundMessage` replies back to the original platform conversation. Feishu, QQBot, and Telegram use Beaver-owned protocol adapters. Weixin personal-account support uses a docker-compose predeclared sidecar connector, so Beaver exposes it as an `ExternalConnectorChannel` rather than a Beaver-owned `WeixinAdapter`.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Use internal adapters by default, but allow external connector processes where platform SDK or login state requires them.
|
||||
- Do not implement WhatsApp in this phase.
|
||||
- Do not replace `ChannelRuntime`, `MessageBus`, or `ChannelManager`.
|
||||
- Do not move platform access policy into `AgentService`.
|
||||
- Do not implement streaming token deltas for these channels in this phase.
|
||||
- Do not promise stable Weixin group support; Weixin group delivery is best-effort only.
|
||||
|
||||
## Architecture
|
||||
|
||||
Keep Beaver's channel runtime as the owner of lifecycle, dedupe, event logging, and agent dispatch.
|
||||
|
||||
```text
|
||||
platform SDK/API or sidecar connector
|
||||
-> {Channel}Adapter or ExternalConnectorChannel bridge endpoint
|
||||
-> ChannelRuntime.accept_inbound()
|
||||
-> MessageBus.inbound
|
||||
-> ChannelRuntime bridge
|
||||
-> AgentService.handle_inbound_message()
|
||||
-> MessageBus.outbound
|
||||
-> ChannelManager.dispatch_outbound()
|
||||
-> {Channel}Adapter.send() or ExternalConnectorChannel.send()
|
||||
-> platform SDK/API or sidecar connector API
|
||||
```
|
||||
|
||||
Adapters own platform-specific transport and delivery details when Beaver directly integrates a platform API. For Weixin, the sidecar owns the platform protocol, QR login, receive loop, send behavior, and login-state persistence. The runtime owns Beaver session identity, dedupe, event logging, and run dispatch in both cases.
|
||||
|
||||
## Shared Adapter Contract
|
||||
|
||||
Each runtime channel implements the existing `ChannelAdapter` protocol:
|
||||
|
||||
```python
|
||||
channel_id: str
|
||||
kind: str
|
||||
mode: str
|
||||
|
||||
async def start() -> None
|
||||
async def stop() -> None
|
||||
async def send(message: OutboundMessage) -> None
|
||||
```
|
||||
|
||||
Each Beaver-owned adapter receives a `ChannelInboundSink` and calls `accept_inbound()` for every normalized user message. `ExternalConnectorChannel` receives inbound Weixin messages through Beaver's connector bridge endpoint, then submits normalized messages to `ChannelRuntime.accept_inbound()`.
|
||||
|
||||
For all four adapters:
|
||||
|
||||
- `kind` is one of `feishu`, `qqbot`, `telegram`, `weixin`.
|
||||
- `account_id` comes from channel config.
|
||||
- inbound messages must include `ChannelIdentity`.
|
||||
- outbound replies route by `message.channel_identity` when present, falling back to `message.session_id`.
|
||||
- unsupported media is represented as text metadata in phase one rather than dropped silently.
|
||||
|
||||
## Channel Configuration
|
||||
|
||||
All channels use the existing `BeaverConfig.channels` map.
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"telegram-main": {
|
||||
"enabled": true,
|
||||
"kind": "telegram",
|
||||
"mode": "polling",
|
||||
"accountId": "bot-main",
|
||||
"displayName": "Telegram Main",
|
||||
"secrets": {
|
||||
"botToken": "..."
|
||||
},
|
||||
"config": {
|
||||
"requireMentionInGroups": true,
|
||||
"maxMessageChars": 4096
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Config keys stay channel-specific inside `config` and `secrets`. The factory chooses the adapter by `kind` and `mode`.
|
||||
|
||||
For sidecar-backed channels, config also includes the connector base URL and bridge settings. Beaver must call the already-running connector HTTP API and must not dynamically create containers or require Docker socket access.
|
||||
|
||||
## Identity Mapping
|
||||
|
||||
All adapters map platform identity into `ChannelIdentity`:
|
||||
|
||||
- `channel_id`: configured Beaver channel id, such as `telegram-main`
|
||||
- `kind`: platform kind
|
||||
- `account_id`: configured account id
|
||||
- `peer_id`: platform chat, group, openid, or user conversation id
|
||||
- `thread_id`: platform topic/thread id when applicable
|
||||
- `peer_type`: `dm`, `group`, `channel`, or platform-specific value
|
||||
- `user_id`: platform sender id when available
|
||||
- `message_id`: platform message id or event id
|
||||
|
||||
The runtime continues to derive sessions as:
|
||||
|
||||
```text
|
||||
<channel_id>:<account_id>:<peer_id>[:<thread_id>]
|
||||
```
|
||||
|
||||
Group sessions can later become per-user or per-thread by adding adapter-level `thread_id` rules without changing `ChannelRuntime`.
|
||||
|
||||
## Adapter Scope
|
||||
|
||||
### FeishuAdapter
|
||||
|
||||
Supports:
|
||||
|
||||
- WebSocket long connection as the preferred mode.
|
||||
- Optional webhook mode if configured.
|
||||
- Direct messages.
|
||||
- Group messages gated by mention or config.
|
||||
- Text outbound replies.
|
||||
- Basic inbound media metadata and cached local file paths when available.
|
||||
|
||||
Configuration:
|
||||
|
||||
- `secrets.appId`
|
||||
- `secrets.appSecret`
|
||||
- `config.domain`: `feishu` or `lark`
|
||||
- `config.connectionMode`: `websocket` or `webhook`
|
||||
- `config.requireMentionInGroups`
|
||||
- `config.allowFrom`
|
||||
- `config.groupAllowFrom`
|
||||
|
||||
### QQBotAdapter
|
||||
|
||||
Supports:
|
||||
|
||||
- Official QQ Bot WebSocket gateway for inbound events.
|
||||
- Official REST API for outbound text replies.
|
||||
- Private C2C messages.
|
||||
- Group messages.
|
||||
- Guild/channel messages when the platform event provides them.
|
||||
- Basic rich media intake as cached local files or text metadata.
|
||||
|
||||
Configuration:
|
||||
|
||||
- `secrets.appId`
|
||||
- `secrets.clientSecret`
|
||||
- `config.markdownSupport`
|
||||
- `config.dmPolicy`
|
||||
- `config.allowFrom`
|
||||
- `config.groupPolicy`
|
||||
- `config.groupAllowFrom`
|
||||
|
||||
### TelegramAdapter
|
||||
|
||||
Supports:
|
||||
|
||||
- Bot API long polling as the default mode.
|
||||
- Optional webhook mode if configured.
|
||||
- Direct messages.
|
||||
- Group messages gated by mention or config.
|
||||
- Text replies with platform-safe formatting and chunking.
|
||||
- Photo/document/audio/video intake as cached local files or metadata.
|
||||
|
||||
Configuration:
|
||||
|
||||
- `secrets.botToken`
|
||||
- `config.mode`: `polling` or `webhook`
|
||||
- `config.webhookUrl`
|
||||
- `config.webhookSecret`
|
||||
- `config.requireMentionInGroups`
|
||||
- `config.allowFrom`
|
||||
- `config.groupAllowFrom`
|
||||
- `config.maxMessageChars`
|
||||
|
||||
### ExternalConnectorChannel For Weixin
|
||||
|
||||
Supports:
|
||||
|
||||
- Docker-compose predeclared sidecar connector.
|
||||
- QR-login sessions started and observed through the sidecar HTTP API.
|
||||
- Direct messages.
|
||||
- Text replies sent through the sidecar `/send` API.
|
||||
- Media send/receive when the sidecar provides normalized metadata.
|
||||
- Group delivery as best-effort only.
|
||||
|
||||
Configuration:
|
||||
|
||||
- `secrets.connectionToken`
|
||||
- `config.accountId`
|
||||
- `config.baseUrl`
|
||||
- `config.bridgeSecret`
|
||||
- `config.dmPolicy`
|
||||
- `config.allowFrom`
|
||||
- `config.groupPolicy`
|
||||
- `config.groupAllowFrom`
|
||||
- `config.maxMessageChars`
|
||||
|
||||
Inbound flow:
|
||||
|
||||
```text
|
||||
Weixin sidecar connector
|
||||
-> Beaver connector bridge endpoint
|
||||
-> ChannelRuntime.accept_inbound()
|
||||
-> MessageBus
|
||||
-> AgentService
|
||||
```
|
||||
|
||||
Outbound flow:
|
||||
|
||||
```text
|
||||
AgentService
|
||||
-> MessageBus outbound
|
||||
-> ExternalConnectorChannel.send()
|
||||
-> Weixin sidecar connector /send
|
||||
```
|
||||
|
||||
The sidecar is the Weixin protocol adapter. Beaver's `ExternalConnectorChannel` only validates bridge calls, normalizes the sidecar event boundary, preserves runtime dedupe/session semantics, and forwards outbound sends to the sidecar HTTP API.
|
||||
|
||||
## Access Control
|
||||
|
||||
Adapters may block inbound messages before calling `accept_inbound()` when the platform has channel-native allowlist settings. Runtime dedupe still applies after adapter admission.
|
||||
|
||||
Initial policy values:
|
||||
|
||||
- `open`: allow matching platform scope.
|
||||
- `allowlist`: require `allowFrom` or `groupAllowFrom`.
|
||||
- `disabled`: ignore inbound messages for that scope.
|
||||
|
||||
Pairing is owned by the connector layer. Platform adapters assume a materialized `ChannelConnection` and adapter-ready runtime config. For Weixin personal-account support, the runtime channel is an `ExternalConnectorChannel`, not a Beaver-owned `WeixinAdapter`.
|
||||
|
||||
## Delivery Semantics
|
||||
|
||||
Inbound:
|
||||
|
||||
- validate required routing fields before submitting to runtime.
|
||||
- preserve raw platform payload in metadata only when useful for debugging.
|
||||
- keep metadata small enough for event logs.
|
||||
- include media paths in metadata and text summaries in `content` when the agent needs to know an attachment exists.
|
||||
|
||||
Outbound:
|
||||
|
||||
- send only final assistant replies in phase one.
|
||||
- chunk messages to platform limits.
|
||||
- mark `delivery_status = "unclaimed"` when a target cannot be resolved.
|
||||
- raise or return delivery failures so `ChannelManager` records `outbound_delivery_failed`.
|
||||
|
||||
## Runtime Status
|
||||
|
||||
`ChannelRuntime.statuses()` should report platform channels with:
|
||||
|
||||
- `channel_id`
|
||||
- `kind`
|
||||
- `mode`
|
||||
- `display_name`
|
||||
- `enabled`
|
||||
- `state`
|
||||
- `account_id`
|
||||
- `last_error`
|
||||
- `last_event_at`
|
||||
- `capabilities`
|
||||
|
||||
Capabilities are conservative:
|
||||
|
||||
- Feishu: `receive_text`, `send_text`, `receive_media`, `groups`
|
||||
- QQBot: `receive_text`, `send_text`, `receive_media`, `groups`
|
||||
- Telegram: `receive_text`, `send_text`, `receive_media`, `groups`
|
||||
- Weixin: `receive_text`, `send_text`, `receive_media`, `direct_messages`
|
||||
|
||||
## Error Handling
|
||||
|
||||
- Adapter startup failure sets channel state to `error` and does not stop other channels.
|
||||
- Runtime shutdown calls every adapter `stop()`.
|
||||
- Platform transient errors should retry inside the adapter only when retrying cannot duplicate user-visible sends.
|
||||
- Fatal credential/config errors should surface in channel status.
|
||||
- Inbound duplicates are handled by existing `ChannelDedupeStore`.
|
||||
|
||||
## Testing
|
||||
|
||||
Add tests in small layers:
|
||||
|
||||
- factory tests for `kind` and `mode` adapter selection.
|
||||
- identity normalization tests for each platform.
|
||||
- inbound adapter tests using fake platform payloads.
|
||||
- outbound adapter tests with fake platform clients.
|
||||
- runtime status tests for configured enabled/disabled/error channels.
|
||||
|
||||
Network live tests are out of scope for unit tests. Adapter constructors should accept injectable clients or lightweight transport functions so tests do not call real platform APIs.
|
||||
|
||||
## Rollout
|
||||
|
||||
Implement one adapter at a time:
|
||||
|
||||
1. Telegram
|
||||
2. Feishu
|
||||
3. QQBot
|
||||
4. Weixin
|
||||
|
||||
Telegram is first because its bot-token flow and text path are the simplest proof of the shared adapter pattern. Weixin is last because QR/login state, context tokens, and media handling are more specialized.
|
||||
Reference in New Issue
Block a user