Skip to content

feat(channels): add first-party Feishu / Lark channel - #4136

Open
himax12 wants to merge 2 commits into
letta-ai:mainfrom
himax12:feat/channels-feishu-lark
Open

himax12 wants to merge 2 commits into
letta-ai:mainfrom
himax12:feat/channels-feishu-lark

Conversation

@himax12

@himax12 himax12 commented Aug 31, 2026

Copy link
Copy Markdown

Summary

  • Add a messenger-only first-party feishu channel (display name Feishu / Lark) over the Open Platform persistent connection (WebSocket). No webhook, cards, Drive, or custom Desktop screens.
  • Pairing and allowlists use Feishu open_id (ou_*). Groups default to mention-only (matches the default @bot event scope). @all is not a bot mention. Bot/app senders are dropped.
  • One running listener per App ID (cluster mode). domain selects Feishu (open.feishu.cn) vs Lark (open.larksuite.com). Text send goes through the shared MessageChannel tool.
  • appId / appSecret / domain land on the existing flattened ChannelPluginAccountPatch (same shared create/update path as Signal), not a new discriminated patch union.

Closes #4130.

Why

Mainland Feishu and international Lark are the same Open Platform with different consoles. A first-party plugin is what makes letta channels configure/install/pair work without a user-plugin manifest. Persistent connection is the local analog of Slack Socket Mode.

Limits / non-goals

  • No published ./channels/feishu package export (Slack/Telegram subpaths shipped because Cloud needed them). Pure ingress.ts / outbound.ts stay SDK-free for a later webhook host.
  • No HTTP webhook, Card Kit, docs/drive/Bitable, store apps, or allowBots.
  • Self-built apps only. Restart the listener after editing accounts.json.

Verification

  • bun run check — 12/12
  • bun test src/channels/feishu src/channels/registry-community-copy.test.ts — 34 passed
  • Lifecycle errors go through formatChannelLifecycleErrorMessage (adapter test covers sanitizing before post).
  • Mention-only drop, message_id dedup, and second App ID start are covered by tests that fail without this change.

Live validation

No live Feishu/Lark app probe was run. Harness-only: injected SDK client, ingress/outbound unit tests, and the App ID uniqueness guard.

AI Disclosure

  • This pull request was written entirely by a human
  • This pull request was written with AI assistance and reviewed and edited by a human
  • I have read the AI Policy and agree to its terms

AI Tool(s) Used

Cursor

Human Verification

I have reviewed and understand every change in this pull request and take responsibility for its correctness.

Made with Cursor

Linear: https://linear.app/letta/issue/LET-11920/add-first-party-feishulark-channel-support

Ship a messenger-only persistent-connection transport so mainland Feishu and international Lark can pair, mention-route, and send text without a Cloud webhook or custom Desktop UI.

Co-authored-by: Cursor <cursoragent@cursor.com>
@himax12
himax12 requested a review from cpacker as a code owner August 31, 2026 03:32
Copilot AI lite review requested due to automatic review settings August 31, 2026 03:32
@himax12
himax12 requested a review from just-cameron as a code owner August 31, 2026 03:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Comment thread src/channels/feishu/ingress.ts Outdated
mentions: FeishuMention[],
botOpenId?: string | null,
): boolean {
if (mentions.some((mention) => mention.mentioned_type === "bot")) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

match on a field the payload does not have. refer https://open.feishu.cn/document/server-docs/im-v1/message/events/receive

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice structure overall — follows the existing channel conventions and skipping
media downloads avoids a lot of trouble. One blocker though.

P0: group @mentions are never detected, so group chat does nothing by default.

isFeishuBotMention() has two ways to detect a mention and both are broken:

// A — field does not exist in the payload
if (mentions.some((m) => m.mentioned_type === "bot")) return true;
// B — never runs: botOpenId is always null
const trimmedBotOpenId = botOpenId?.trim();
if (!trimmedBotOpenId) return false;
return mentions.some((m) => m.id?.open_id === trimmedBotOpenId);

@himax12 himax12 Aug 31, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — thanks for catching this. mentioned_type isn't on the live im.message.receive_v1 mention object, and we were also passing botOpenId: null, so both checks were dead and mention-only groups dropped everything.

Fixed in d6fcaa4: start calls GET /open-apis/bot/v3/info, then isFeishuBotMention() matches mentions[].id.open_id against that bot open_id. mentioned_type is ignored. Tests cover the official-shaped payload (no mentioned_type), a mentioned_type: bot red herring.

Match mentions[].id.open_id from bot/v3/info instead of mentioned_type, which is not present on live im.message.receive_v1 payloads.

Co-authored-by: Cursor <cursoragent@cursor.com>
@himax12
himax12 requested a review from lipracer September 1, 2026 05:34
@lipracer

lipracer commented Sep 1, 2026

Copy link
Copy Markdown

Re-reviewed after the update — the P0 is resolved, and the fix is more thorough
than what I suggested. Removing mentioned_type from the FeishuMention
interface (not just from the check), extracting parseFeishuBotInfo so
account-display.ts reuses it, and clearing botOpenId on all three teardown
paths all look right.

The regression test is the part I'd single out: keeping a fixture that still
carries mentioned_type: "bot" while pointing open_id at a different user,
and asserting the message is dropped, locks the bug out permanently rather than
just fixing it.

One new point — and a correction to my earlier suggestion

I argued for failing start() when /open-apis/bot/v3/info fails. You chose to
warn and continue, and you're right that hard-failing would break DM-only setups
over a call that path doesn't need.

The current shape has no recovery path, though. A single transient failure at
startup leaves botOpenId null for the whole session, and because isRunning()
reports true the channel looks healthy while every group @mention is silently
dropped. The only clue is one startup warning that has long scrolled away.

A lazy retry covers both cases better than either fail-fast or warn-once: on the
first mention-only group event, if botOpenId is still null, resolve it again
with simple backoff — only on the group path, so DMs stay unaffected. Transient
failures then self-heal, and persistent ones keep producing log lines instead of
a single one that was easy to miss.

Still open from the earlier pass

Both non-blocking as far as I'm concerned:

  • senderName is resolved by searching mentions for the sender, but that array
    holds mentioned users, so in practice it falls back to the raw ou_... id.
  • start() sets running = true before the socket settles and installs no
    close/error handler, so isRunning() stays true after a mid-session
    disconnect. Worth confirming whether WSClient reconnects internally.

Standing

I'm not a code owner on this repo and can't approve or gate the merge — please
treat this as a drive-by review from someone who wants this channel to land, not
as a required sign-off.

Reviewed with Comate CLI (model: Opus 5). Findings were verified
against the PR diff and the Feishu im.message.receive_v1
documentation; no live tenant test was run on my side either.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Feishu (Lark) Channel Support

4 participants