Skip to content

fix: wait for a participant to become active, not just connected - #2403

Draft
tinalenguyen wants to merge 1 commit into
mainfrom
tina/wait-active-participant
Draft

fix: wait for a participant to become active, not just connected#2403
tinalenguyen wants to merge 1 commit into
mainfrom
tina/wait-active-participant

Conversation

@tinalenguyen

Copy link
Copy Markdown
Member

Draft — blocked on livekit/node-sdks#715. CI will fail until that ships and the @livekit/rtc-node catalog pin is bumped; ParticipantState and RoomEvent.ParticipantActive don't exist in any published version yet.

Problem

waitForParticipant resolves on RoomEvent.ParticipantConnected, which fires while the participant is still JOINING/JOINED. But a remote participant can only receive data messages once it reaches ParticipantState.ACTIVE. So callers get back a participant that is present in room.remoteParticipants yet not reachable, and anything sent to it is silently dropped.

The most visible case is DataStreamAudioOutput (agents/src/voice/avatar/datastream_io.ts:129), which waits on this before streaming audio to an avatar worker — the wait can return early and publish into a void.

Python has always waited on participant_active here (utils/participant.py): it listens on that event and requires p.state == PARTICIPANT_STATE_ACTIVE when scanning participants already in the room. JS had no equivalent because the Node SDK never surfaced the event — hence the companion PR.

Changes

agents/src/utils.tswaitForParticipant now:

  • listens on RoomEvent.ParticipantActive instead of ParticipantConnected
  • requires state === ParticipantState.ACTIVE when scanning room.remoteParticipants

includeLocal is deliberately untouched, matching Python: the local participant has no remote lifecycle to wait on.

Every caller benefits — ctx.waitForParticipant, DataStreamAudioOutput, and AvatarSession.

Tests

Eight new tests in agents/src/wait_for_participant.test.ts: immediate return for an already-active participant, continued waiting for connected-but-not-active (the actual bug), resolution via the event for a late joiner, identity filtering, kind filtering, disconnect rejection, abort rejection, and the local-participant path.

Full suite passes — 2330 tests across 143 files. Lint and prettier clean.

pnpm api:check fails, but identically on unmodified mainetc/agents.api.md is stale (interruptionDetection ordering, _redactionEnabled, RimeModels, _holdInterruptions). Verified by stashing and re-running. I left that drift alone rather than sweeping it into this PR; there is no participant-related API change.

Merge order

  1. feat(rtc): surface ParticipantActive and Participant.state node-sdks#715 lands and releases
  2. bump @livekit/rtc-node in pnpm-workspace.yaml
  3. mark this ready

Note on the bey retry loop

This does not on its own fix the symptom that #2340 patches with a retry loop. That loop's log blames transient disconnect/reconnect, but in rtc-node RoomEvent.Disconnected is terminal — reconnects go through Reconnecting/Reconnected. The likelier trigger there is waitForParticipant's synchronous throw new Error('Room is not connected') during a reconnect window, which this PR doesn't touch. What this fixes is the quieter bug underneath: audio published to a connected-but-not-yet-active avatar. Worth re-testing the bey case against this to see whether the plugin-level retry is still needed.

`waitForParticipant` resolved on `RoomEvent.ParticipantConnected`, which fires
while the participant is still JOINING/JOINED. A remote participant can only
receive data messages once it reaches `ParticipantState.ACTIVE`, so callers got
back a participant that is present in `room.remoteParticipants` but not yet
reachable, and anything sent to it was silently dropped.

That is most visible in `DataStreamAudioOutput`, which waits on this before
streaming audio to an avatar worker — the wait could return early and publish
into a void.

Port the Python SDK's semantics, which have always waited on `participant_active`:
resolve on `RoomEvent.ParticipantActive`, and when scanning participants that are
already in the room require `state === ParticipantState.ACTIVE`. The local
participant (`includeLocal`) is unaffected — it has no remote lifecycle to wait on.

Depends on the matching @livekit/rtc-node change surfacing `RoomEvent.ParticipantActive`
and `Participant.state`; this cannot merge until that ships.
@changeset-bot

changeset-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e68a8dd

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 39 packages
Name Type
@livekit/agents Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-anthropic Patch
@livekit/agents-plugin-assemblyai Patch
@livekit/agents-plugin-azure Patch
@livekit/agents-plugin-baseten Patch
@livekit/agents-plugin-bey Patch
@livekit/agents-plugin-cartesia Patch
@livekit/agents-plugin-cerebras Patch
@livekit/agents-plugin-deepgram Patch
@livekit/agents-plugin-did Patch
@livekit/agents-plugin-elevenlabs Patch
@livekit/agents-plugin-fishaudio Patch
@livekit/agents-plugin-google Patch
@livekit/agents-plugin-hedra Patch
@livekit/agents-plugin-hume Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-krisp Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-liveavatar Patch
@livekit/agents-plugin-livekit Patch
@livekit/agents-plugin-minimax Patch
@livekit/agents-plugin-mistral Patch
@livekit/agents-plugin-mistralai Patch
@livekit/agents-plugin-neuphonic Patch
@livekit/agents-plugin-openai Patch
@livekit/agents-plugin-perplexity Patch
@livekit/agents-plugin-phonic Patch
@livekit/agents-plugin-protoface Patch
@livekit/agents-plugin-resemble Patch
@livekit/agents-plugin-rime Patch
@livekit/agents-plugin-runway Patch
@livekit/agents-plugin-sarvam Patch
@livekit/agents-plugin-silero Patch
@livekit/agents-plugin-soniox Patch
@livekit/agents-plugin-tavus Patch
@livekit/agents-plugins-test Patch
@livekit/agents-plugin-trugen Patch
@livekit/agents-plugin-xai Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

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.

1 participant