# Spec: Feishu channel extension ## Requirements ### Requirement: Channel plugin availability The system SHALL provide a Feishu chat channel as a Clawdbot extension plugin that can be installed and enabled without modifying core channel code. #### Scenario: Plugin appears in onboarding catalog - **GIVEN** a user runs `clawdbot onboard` in a workspace that contains the Feishu plugin (local path) or can access it on npm - **WHEN** the user reaches the channel selection step - **THEN** Feishu is listed as an installable channel plugin with a docs link ### Requirement: Webhook endpoint and URL verification (mode: http) The system SHALL accept Feishu event subscription callbacks over HTTP and complete the platform “request URL verification” handshake. #### Scenario: URL verification succeeds - **GIVEN** Feishu sends a `type="url_verification"` callback payload with a `challenge` - **WHEN** Clawdbot receives the POST at the configured webhook path - **THEN** the response status is `200` and the response body is `{"challenge":""}` (JSON) ### Requirement: Request validation (mode: http) The system SHALL validate inbound callback requests before processing events. #### Scenario: Invalid signature/token is rejected - **GIVEN** a callback request with an invalid signature (encrypted mode) OR mismatched verification token (non-encrypted mode) - **WHEN** the request is received - **THEN** the request is rejected with `401` and no message processing occurs ### Requirement: Encrypted payload support (mode: http) When configured with an encrypt key, the system SHALL decrypt payloads that use the `encrypt` envelope. #### Scenario: Encrypted event is processed - **GIVEN** a callback request containing an `encrypt` field - **WHEN** the plugin is configured with the correct `encryptKey` - **THEN** the decrypted JSON is used for URL verification and event handling ### Requirement: Inbound message handling (DM) The system SHALL process `im.message.receive_v1` DMs and route them into the Clawdbot agent pipeline with DM security policies. #### Scenario: Unknown DM triggers pairing flow - **GIVEN** `channels.feishu.dm.policy="pairing"` - **AND** a DM sender is not allowlisted and not previously paired - **WHEN** the sender DMs the bot - **THEN** the system records a pairing request and replies with a pairing code message ### Requirement: Inbound message handling (groups) The system SHALL process `im.message.receive_v1` group messages with group allowlists and mention gating. #### Scenario: Group message is mention-gated - **GIVEN** `channels.feishu.groupPolicy="open"` (or allowlisted group) - **AND** `requireMention=true` - **WHEN** a group message arrives without mentioning the bot - **THEN** the system ignores the message and does not invoke the agent ### Requirement: Outbound text delivery The system SHALL be able to send text messages to Feishu users and group chats. #### Scenario: CLI message send delivers text - **GIVEN** the user runs `clawdbot message send --to --message "hi"` - **WHEN** the target is a valid Feishu user (`open_id`) or chat (`chat_id`) - **THEN** the plugin sends a Feishu API request that results in a visible message in the correct conversation ### Requirement: Status and probe visibility The system SHALL expose Feishu channel health via `clawdbot channels status`, including an active probe that validates credentials. #### Scenario: Probe fails with actionable error - **GIVEN** the plugin is enabled but credentials are invalid - **WHEN** the user runs `clawdbot channels status --probe` - **THEN** the Feishu channel shows `probe=error` with an actionable message (e.g. token fetch failed) ### Requirement: Long connection mode (mode: ws) The system SHALL support receiving Feishu events over long connection when `channels.feishu.mode="ws"` is configured. #### Scenario: Long connection receives events without public URL - **GIVEN** `channels.feishu.mode="ws"` and `channels.feishu.appId/appSecret` are configured - **WHEN** the gateway starts - **THEN** the plugin establishes a long connection to Feishu and can receive `im.message.receive_v1` events without an exposed webhook URL