Skip to content

feat(meeting): add meeting channel with follow and join entry points - #18

Merged
mazhe-nerd merged 1 commit into
mainfrom
feature/add-meeting-channel-with-uat-follow-and-tat-join
Aug 28, 2026
Merged

feat(meeting): add meeting channel with follow and join entry points#18
mazhe-nerd merged 1 commit into
mainfrom
feature/add-meeting-channel-with-uat-follow-and-tat-join

Conversation

@mazhe-nerd

Copy link
Copy Markdown
Collaborator

What this adds

A meeting channel, so a bot can act on what happens during a meeting —
captions, in-meeting chat, participants joining and leaving, shared documents.

Two entry points, one session type. Moving from one to the other changes the
entry-point line and nothing else.

# The bot joins as a real participant and can speak in the meeting
channel.on("meetingInvited", lambda inv: channel.join_meeting(inv.meeting_no))

# Or follow the meeting a user is currently in, without joining it
session = await channel.follow_my_meeting(user_open_id="ou_...")

session.on("transcript", lambda e: notes.append(e.text))
session.on("chat", on_chat)
await session.send_message("noted")   # joined sessions only
await session.leave()
follow_my_meeting join_meeting
Visible in the meeting no yes, a real participant
Credential the user's own access token the app's tenant token
How content arrives polling pushed events
Needs connect() no — REST only yes
Can speak in the meeting no, reply over IM yes, send_message

Both require the meeting's "allow agents to join" setting; joining as the bot
is additionally gated behind an application process.

Two smaller additions came out of the same work:

  • on_raw_event(event_type, handler) — subscribe to any Feishu event type the
    channel does not wrap, and have it take effect on the dispatcher that is
    already running.
  • get_meeting_event_health() — counters for diagnosing silence on this path.
    An undeclared subscription, a missing permission and a renamed field all look
    like "nothing happened", so the counters separate them.

Notes for integrators

  • follow_my_meeting reads a meeting under a user's own authorization, and
    the bot is not visible in the meeting. The user_open_id you pass is the
    identity the SDK acts as; the SDK receives a string and cannot verify whose it
    is. MeetingChannelConfig.follow_allowlist gates it and defaults to open.
  • meetingInvited and on_raw_event do not pass through PolicyConfig,
    SeenCache dedup, the processing lock or the loop guard. Both default to
    open; MeetingChannelConfig.invite_allowlist gates the former.
  • dispose() does not leave a meeting, which is what makes reconnects safe. So
    disconnect() leaves the bot in its meetings, and a process that is really
    exiting should leave() each session first.
  • The bot's own in-meeting messages come back as meeting chat, so a chat handler
    needs if event.self_echo: return.

docs/meeting-channel.md covers ordering, caption settling, session limits and
reclamation, and diagnostics. docs/security.md gained sections on user access
tokens and on the entry points that sit outside the message policy.

Compatibility

Additive only — nothing existing changed or was removed. Version 1.2.0 → 1.3.0.

New code imports only the standard library, and the package still installs
alongside lark-oapi.

Tests

1308 unit tests pass. 206 of them are new and live in
lark_channel/channel/meeting/tests/, covering event normalization across both
wire shapes the platform uses, dedup and caption settling, seat accounting and
reclamation evidence, credential hygiene (including values reachable through a
raised error's __traceback__ frame locals), event-loop affinity, and the
raw-event registry. Two runnable samples are included under samples/channel/.

@mazhe-nerd
mazhe-nerd force-pushed the feature/add-meeting-channel-with-uat-follow-and-tat-join branch from 8a2b29f to 8a3c01f Compare August 25, 2026 07:06
"""A single-consumer queue that awaits each handler in turn."""

def __init__(self, *, on_handler_error: Callable[[BaseException], Any]) -> None:
self._queue: "asyncio.Queue[Optional[Tuple[List[Callable], Any]]]" = (

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

这个 Queue 加一下加一下容量上限吧

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done — the queue is now bounded at MAX_QUEUED_DELIVERIES = 1000 per session.

Two things fell out of deciding which end to drop, so I want to flag them
rather than leave them implicit:

Overflow rejects the newest, not the oldest. Order is the guarantee this
queue exists for — a document swap arrives as magic_share_ended then
magic_share_started — so evicting from the front would split such a pair and
hand the application a queue that still looks complete. Blocking the producer
was not available either: it would park the socket's message handler or the poll
loop, and with one thread that stalls every meeting and the heartbeat with it.
Rejecting at the tail keeps what is queued contiguous, so a gap is a gap at the
end.

Error reports get headroom above the ceiling (REPORT_RESERVE = 32).
Teardown submits here — the end event and any error raised by the departure
call — and those are what explain why a session went away. A ceiling filled with
transcripts must not be able to drop the explanation and keep the noise. The
reserve is bounded too, so a handler that raises on every delivery cannot grow
the queue through the reports about it.

Drops are counted and surface as dropped on get_meeting_event_health(), with
a row in the diagnostics table in docs/meeting-channel.md. A silent drop is
exactly the failure that readout exists to make visible.

Five tests cover it, each verified to fail against the corresponding wrong
implementation: no ceiling, evict-oldest, no report headroom, unbounded
headroom, and drops from retired sessions not reaching the readout.

@mazhe-nerd
mazhe-nerd force-pushed the feature/add-meeting-channel-with-uat-follow-and-tat-join branch from 8a3c01f to d6b2de7 Compare August 28, 2026 04:23
@mazhe-nerd
mazhe-nerd merged commit 7eec2f1 into main Aug 28, 2026
7 checks passed
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