Skip to content

feat(feishu): add CLI channel and setup guidance#2185

Closed
wade19990814-hue wants to merge 12 commits into
esengine:mainfrom
wade19990814-hue:codex/feishu-cli-channel
Closed

feat(feishu): add CLI channel and setup guidance#2185
wade19990814-hue wants to merge 12 commits into
esengine:mainfrom
wade19990814-hue:codex/feishu-cli-channel

Conversation

@wade19990814-hue

@wade19990814-hue wade19990814-hue commented May 28, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add a CLI-only Feishu channel with /feishu connect|status|disconnect
  • add Feishu config persistence and a small built-in Feishu setup helper to reduce repeated maintainer/user support load
  • keep the scope intentionally narrow: no desktop runtime, no settings UI, no remote command surface beyond the existing CLI-first channel behavior
  • require explicit access control by default (ownerOpenId or allowlist); no first-sender auto-binding
  • keep QQ/remote model messaging aligned with the current two-model CLI surface

Why this scope

Feishu support has been requested explicitly in:

This PR only takes the smallest reviewable slice of that demand: a CLI-first text channel with staged onboarding and status/teardown, without mixing in desktop work or a larger remote-control surface.

Notes

  • This branch is rebased onto the latest main.
  • The default access model is now fail-closed: if neither ownerOpenId nor an allowlist is configured, the channel will not accept messages.
  • A built-in feishu helper is included so users can get setup/troubleshooting guidance in-product, similar to the recent QQ workflow improvements.

Testing

  • npm run verify

@wade19990814-hue

Copy link
Copy Markdown
Contributor Author

A quick scope note for reviewers:

  • This PR is intentionally the smallest Feishu slice that still addresses the explicit requests in 希望能增加对飞书渠道的支持 #1447, 企业微信/飞书集成 MCP Server #1837, and 既然接了QQ,桌面可以接入飞书吗? #1332.
  • I kept it CLI-only on purpose to reduce review load: no desktop runtime wiring, no desktop settings UI, and no extra transport surfaces beyond the existing QQ/CLI-style remote-message path.
  • The user-facing additions in this pass are the minimum adoption pieces: Feishu config support, /feishu connect|status|disconnect, and the builtin eishu setup/troubleshooting guide for users who would otherwise need maintainer hand-holding.
  • I also fixed the Windows directory-link issue in ests/skills.test.ts, so local
    pm run verify now passes on this branch instead of stopping at that unrelated EPERM case.

So the goal here is not to land "all of Feishu", but to make the first reviewable step small, explicit, and support-friendly.

@esengine

Copy link
Copy Markdown
Owner

This is a sizable new channel (+2620/-50, 35 files) and it currently conflicts with main (DIRTY) — several PRs landed during this review pass. Please rebase onto current main first so I can review the real diff. When you do: given it's a network-facing channel like the Telegram one (#2168), please make sure access control fails closed by default (refuse to start without an explicit owner/allowlist rather than accepting the first sender), since the channel can drive the agent's file/shell tools. I'll do a full pass — access model + message-dispatch path — once it's conflict-free.

@wade19990814-hue wade19990814-hue force-pushed the codex/feishu-cli-channel branch from 9016d70 to 72d3fca Compare May 29, 2026 03:38
@wade19990814-hue

Copy link
Copy Markdown
Contributor Author

Addressed the requested changes.

  • rebased onto the latest main
  • changed Feishu default access from QQ-style first-sender binding to fail-closed explicit access (ownerOpenId or allowlist required)
  • kept the PR CLI-only so the review surface stays narrow
  • reran full verification locally: npm run verify

I also tightened the PR description so it reflects the actual reviewable scope more clearly.

@wade19990814-hue wade19990814-hue marked this pull request as draft May 29, 2026 04:09

@esengine esengine left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Reviewed the rebased version — solid, and it mirrors the (now-hardened) Telegram channel structure. Key security checks pass:

  • Fail-closed access: decideFeishuAccess gates on owner/allowlist and ends in { accept: false, reason: "unauthorized" } — no open-mode fallback, so an unconfigured channel rejects everyone.
  • Authenticated ingress: events arrive over Lark.WSClient (outbound, app-credential-authenticated long connection via @larksuiteoapi/node-sdk) through EventDispatcher for im.message.receive_v1 — there's no inbound webhook endpoint to forge POSTs to, so no signature-verification gap (the SDK's authenticated WS connection is the trust boundary, analogous to Telegram's polling).
  • Dispatch is gated on acceptRemoteInput before a message reaches the agent, so the tool-driving surface is contained to authorized open_ids.

Official Lark SDK is a reasonable dependency, mirrors the proven Telegram pattern, tests cover access/channel/config/first-connect/slash, CI green. Merging.

@esengine

Copy link
Copy Markdown
Owner

Correction to my approval above — I went to merge but it's still marked draft, so GitHub blocked it. The review stands (fail-closed access, authenticated WSClient ingress, gated dispatch, CI green). Whenever you're ready, mark it 'Ready for review' (un-draft) and I'll merge — or let me know if you're still iterating.

@wade19990814-hue wade19990814-hue marked this pull request as ready for review May 29, 2026 05:08
@wade19990814-hue wade19990814-hue force-pushed the codex/feishu-cli-channel branch from b8dbf23 to 7a18fb5 Compare May 29, 2026 05:16

Copy link
Copy Markdown
Contributor Author

Pushed a follow-up markdown rendering fix in 30a24677.

This specifically addresses the case where Feishu was showing an interactive/lark_md card shell but still rendering the model's raw markdown source (#, fenced code blocks, tables, etc.) almost verbatim inside it.

What changed:

  • unwrap a full outer markdown / md fence before delivery
  • normalize headings into Feishu-friendlier emphasis
  • downgrade markdown tables into readable key/value bullet lines
  • downgrade fenced code blocks into labeled plain text blocks
  • keep the existing markdown-first / plain-text-fallback transport behavior

I reran:

  • npm test -- --run tests/feishu-channel.test.ts tests/feishu-first-connect.test.tsx tests/feishu-slash.test.ts
  • npm run typecheck
  • npm run verify

All are passing on this branch now.

Copy link
Copy Markdown
Contributor Author

Follow-up fix pushed in a58f39f3.

This addressed a real multi-channel routing bug when QQ and Feishu were both enabled in CLI:

  • QQ-origin messages were being accepted by the app, but their fromQQ marker could be lost during the qq -> telegram -> feishu submit parsing chain
  • that meant the turn still ran locally, but the final reply no longer routed back to QQ

What changed:

  • added a small shared remote-ingress resolver so the first recognized remote submission is preserved instead of being reparsed by later channels
  • added tests/remote-incoming.test.ts to lock the regression down
  • reran the Feishu-focused tests, npm run typecheck, and full npm run verify

So with this patch, QQ + Feishu can stay connected at the same time without QQ replies being dropped after Feishu has been used in the same CLI session.

Copy link
Copy Markdown
Contributor Author

@esengine I think this one is finally in a good place.

At this point I’ve gone through the Feishu CLI channel end-to-end and checked the remaining issues I could reasonably think of on this branch:

  • rebased to latest main
  • switched the default access model to fail-closed
  • kept the scope CLI-only to reduce review surface
  • added /feishu connect|status|disconnect
  • added the built-in Feishu setup/help guidance for first-time users
  • updated the setup flow to point at the OpenClaw "方式一" entry and the official open_id doc
  • tightened staged first-connect input handling
  • aligned the remote /model choices with current main
  • improved Feishu markdown rendering instead of only wrapping raw markdown in a card shell
  • fixed the multi-channel QQ+Feishu reply-routing bug where QQ-origin replies could be dropped once both channels were enabled
  • reran full npm run verify

So from my side, this PR is no longer just “add a Feishu transport” — it now includes the follow-through needed to make the CLI flow, setup path, rendering, and mixed-channel behavior feel coherent with the current QQ work.

I’m sure there can always be more future polish, but for the current intended scope, I believe I’ve checked the main integration and UX edges I could think of.

Thanks again for the review guidance — especially on the access model. This one took a few rounds, but I think it finally landed in the right shape.

@esengine esengine left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks for the thorough iteration on this — the core Feishu channel is well structured and the security model is exactly what was asked for. I re-verified the rebased head directly against source and the fail-closed guarantees hold:

  • decideFeishuAccess (src/feishu/access.ts) rejects an empty sender and ends in the {accept:false, reason:"unauthorized"} fallback for both the configured-but-unmatched and the wholly-unconfigured cases — no open / first-sender / bindRuntime mode. tests/feishu-access.test.ts pins decideFeishuAccess({}, ...) === reject.
  • start() (channel.ts) additionally throws and refuses to launch when no owner and an empty allowlist, releasing the lock.
  • Ingress is the authenticated Lark.Client + Lark.WSClient + EventDispatcher for im.message.receive_v1, p2p-gated, with a bounded dedup set — no forgeable inbound webhook.
  • Dispatch is gated on decideFeishuAccess in handlePrivateMessage before onSubmitMessage reaches the agent, logging only a redacted openId. Nicely mirrors the hardened Telegram channel.

Two blockers before this can merge:

  1. CI is RED on both build (ubuntu) and build (windows). You correctly bumped the slash-command counts 47 -> 48 for /feishu, but the live bare-slash surface is now 49, so tests/slash.test.ts ("suggestSlashCommands filters by prefix") and tests/ui-slash-suggestions.test.tsx ("renders the bare slash release command surface as 48 total commands") both fail: "expected length 48 but got 49". Please reconcile against current main and update the counts (and the "48 commands"/title) to the true value, then re-run npm run verify so both jobs go green. (The Windows-only auto-git-rollback timeout looks flaky/unrelated — a re-run should clear it.)

  2. src/skills.ts line 1 picked up a stray UTF-8 BOM (the  before the leading /** comment) — looks like an editor artifact. Please strip it so the file is plain UTF-8 again.

A couple of non-blocking notes: this PR also refactors the shared QQ/Telegram/Weixin submit path (App.tsx + new remote-incoming.ts) and carries a Windows symlink-type test fix — a bit beyond "add a Feishu channel"; splitting those out would keep the surface tight, but I'm fine keeping them if you note them in the description. Also: storing the bot appSecret in the main config.json (via saveFeishuConfig) matches the existing QQ/Weixin/Telegram convention, so no change needed here, but a future move to a dedicated secret store would be worthwhile across all channels. Finally, my earlier approval is stale (it cited green CI, which no longer holds) — I'll re-approve once CI is green.

@wade19990814-hue wade19990814-hue force-pushed the codex/feishu-cli-channel branch from ce62b03 to 95072c5 Compare May 29, 2026 09:12
The static top-level import * as Lark from '@larksuiteoapi/node-sdk'
caused the entire module tree (bot.ts → channel.ts → chat.tsx) to crash
at load time on CI runners where the SDK failed to install (Windows).
This cascaded into 50 test files failing with:

  Error: Failed to load url @larksuiteoapi/node-sdk

Switch to async import() inside start() so the SDK is only resolved
when a user actually connects to Feishu. If the package is absent,
a clear error message tells the user how to install it.

Moves Lark.Client construction from the constructor into start() so
the dynamically loaded module is available. Adds a guard in
sendPrivateMessage() for calls before start().
@esengine

Copy link
Copy Markdown
Owner

Holding for a focused review pass — 2800 lines across 29 files is more than I want to land in the release window. The Feishu CLI channel design looks reasonable on a skim; I'd just rather take it after the cut so the diff doesn't compete for attention with the other release PRs. Leaving open, will revisit.

@wade19990814-hue

Copy link
Copy Markdown
Contributor Author

Totally understand, and I completely agree that keeping the release window clean is the priority!

I just wanted to gently flag that since this branch has already absorbed multiple rebases (including proactive work like unifying the remote message loops to avoid future channel divergence), keeping it aligned with main has been quite active. I’m a bit worried that waiting too long after the release might lead to tricky rebase conflicts down the road, which would make the review workload heavier for both of us.

Whenever you have a window after the release cut, I’d really appreciate getting this reviewed and landed. If there’s any way I can help make the review smoother or if you'd like me to break anything down, just let me know! Thanks again for all your guidance.

@wade19990814-hue wade19990814-hue deleted the codex/feishu-cli-channel branch May 29, 2026 15:43
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.

2 participants