Skip to content

fix(voice): require can_manage_agent_session grant for remote session#1459

Closed
toubatbrian wants to merge 1 commit into
mainfrom
claude/quirky-galileo-OXltI
Closed

fix(voice): require can_manage_agent_session grant for remote session#1459
toubatbrian wants to merge 1 commit into
mainfrom
claude/quirky-galileo-OXltI

Conversation

@toubatbrian
Copy link
Copy Markdown
Contributor

Summary

Automated port of livekit/agents#5487 ("require CanManageAgentSession grant for remote session") into agents-js. Switches RoomSessionTransport from a single-linked-identity authorization model to a permission-gated one driven by the remote participant's can_manage_agent_session grant.

This was identified by the porting routine as a core runtime improvement (remote session wire protocol, not a plugin or python-specific change), so it is being ported.

This PR is an automated Claude Code routine created by @toubatbrian. Right now it is in experimentation stage.

Ported behavior

Before this change, RoomSessionTransport derived "the remote peer" from RoomIO.linkedParticipant?.identity:

  • Inbound: messages from any other participant identity were silently dropped.
  • Outbound: messages were unicast to that identity if set, otherwise broadcast.

After this change (matching the python PR):

  • Inbound filtering is permission-based. Byte-stream messages are accepted only from participants whose ParticipantPermission.canManageAgentSession is true. Messages from unauthorized participants are logged at debug level and dropped.
  • Outbound delivery with no destinationIdentity broadcasts to all remote participants that hold the can_manage_agent_session grant. When called with an explicit destinationIdentity, the transport verifies that participant still has the grant before sending (drops the message silently otherwise — matching python).
  • Request/response routing. Inbound transport messages now flow as IncomingMessage envelopes that carry the sender's identity alongside the protobuf message. SessionHost uses that identity to send each SessionResponse only back to the requester rather than fanning out to every authorized peer. Broadcast events (AgentSessionEvent) continue to use the default (no destination) path so every authorized peer receives them.

File-level changes

  • agents/src/voice/remote_session.ts
    • New IncomingMessage interface ({ message, senderIdentity? }) — direct mapping of the python IncomingMessage dataclass.
    • New SendMessageOptions ({ destinationIdentity? }) passed as an optional second arg to SessionTransport.sendMessage. Mirrors the python destination_identity keyword.
    • SessionTransport's async iterator now yields IncomingMessage instead of raw pb.AgentSessionMessage.
    • RoomSessionTransport:
      • Constructor no longer takes RoomIO — it now reads authorization directly from room.remoteParticipants and participant.info.permission.canManageAgentSession.
      • Added canManage(identity) and authorizedIdentities() helpers.
      • onByteStream filters by canManage; readStream threads senderIdentity into the enqueued IncomingMessage.
      • sendMessage picks destinationIdentities from the option or from the authorized set; early-returns if there is nobody authorized to send to (so we don't broadcast to the whole room).
    • SessionHost.recvLoop unwraps IncomingMessage and dispatches handleRequestSafe(incoming).
    • handleRequest* family + sendResponse now accept the IncomingMessage so each response is targeted at incoming.senderIdentity. The error path in handleRequestSafe reuses sendResponse rather than building the message inline (matches the python _send_response refactor).
    • RemoteSession.recvLoop unwraps IncomingMessage when dispatching events/responses.
    • RemoteSession.fromRoom(room) no longer requires a RoomIO argument.
  • agents/src/voice/agent_session.ts
    • Updated the single call site to new RoomSessionTransport(room) (dropped the this._roomIO argument).
  • .changeset/require-manage-agent-session-grant.md — patch changeset for @livekit/agents.

Implementation nuances vs. python

Most of the change ports 1:1, but a few JS specifics are worth noting:

  1. Permission access path. Python reads participant.permissions.can_manage_agent_session. The Node SDK doesn't surface a permissions getter on Participant; the grant lives on the underlying proto at participant.info.permission?.canManageAgentSession (singular permission, on ParticipantInfo). The port uses that path. Because canManageAgentSession is typed as boolean | undefined in @livekit/rtc-ffi-bindings, the helper explicitly compares === true so an unset permission is treated as "deny", same as python's required-proto-bool semantics.
  2. No roomIO dependency. The original JS implementation took a RoomIO reference solely to read linkedParticipant. Since identity is now derived from room.remoteParticipants directly, the RoomIO parameter is removed from RoomSessionTransport and RemoteSession.fromRoom. RemoteSession is marked @experimental so this small surface change is treated as a patch-level breaking change behind the experimental flag, consistent with the python PR also reshaping its public RoomSessionTransport constructor (removed remote_identity).
  3. Response routing wire shape. Python's _send_response sets response.request_id = incoming.message.request.request_id and then builds the outer envelope. JS protobuf-es prefers new pb.SessionResponse({ requestId, response, error }), so the helper extracts requestId from the incoming envelope and constructs the response in one go — including the response oneof only when defined (so the internal-error path produces { requestId, error: 'internal error' } with the oneof unset, matching python).
  4. Logging. Python logs via extra={"participant": ...}; the JS port uses pino's structured logging at log().debug({ participant }, '...') — semantically identical, just the idiomatic JS form.
  5. No test file to mirror. The python PR rewrites tests/test_session_host.py to thread IncomingMessage. agents-js doesn't currently have an analogous remote_session.test.ts, so no test edits are needed; existing voice tests still pass (pnpm test -- --run agents/src/voice → 169/169).

Validation

  • pnpm --filter @livekit/agents build — succeeds.
  • pnpm --filter @livekit/agents lint — no new warnings introduced (only preexisting ones remain).
  • pnpm test -- --run agents/src/voice — 169/169 passing.
  • pnpm --filter @livekit/agents api:check — fails with a preexisting export * as ___ error unrelated to this PR (reproduced on main).

Test plan

  • Manual: spin up a RemoteSession against an agent with a participant whose token does not include canManageAgentSession — verify it cannot send or receive session messages.
  • Manual: with two participants both holding canManageAgentSession, verify each one independently sees broadcast events and gets responses routed only to themselves.
  • CI build + tests green.

cc @toubatbrian @livekit/agent-devs for review.

https://claude.ai/code/session_016YNx8jSeTRJJKNujLFy5Ut


Generated by Claude Code

Port of livekit/agents#5487. `RoomSessionTransport` now gates inbound
and outbound session messages on each remote participant's
`can_manage_agent_session` permission instead of a single linked
identity. Responses are routed back to the originating sender via a
new `IncomingMessage` envelope, while broadcast events still fan out
to every authorized peer.

https://claude.ai/code/session_016YNx8jSeTRJJKNujLFy5Ut
@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 11, 2026

🦋 Changeset detected

Latest commit: 04228a5

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

This PR includes changesets to release 31 packages
Name Type
@livekit/agents Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-assemblyai 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-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-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-phonic 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-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

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 04228a58be

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (!this.canManage(options.destinationIdentity)) return;
destinationIdentities = [options.destinationIdentity];
} else {
destinationIdentities = this.authorizedIdentities();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Allow RemoteSession to address agent participants

When RemoteSession.fromRoom() sends a request, sendRequest() calls transport.sendMessage(msg) without a destination, so this branch only targets remote participants whose own permission includes canManageAgentSession. In the default worker registration, agent participants are granted agent: true but not canManageAgentSession (agents/src/worker.ts), because that grant belongs to the controller/client token. As a result, a RemoteSession connected to a normal agent room has an empty destination list and every request times out (and the inbound check also ignores agent responses for the same reason).

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

⚠️ 1 issue in files not directly in the diff

⚠️ New types IncomingMessage and SendMessageOptions not re-exported from voice/index.ts (agents/src/voice/index.ts:14-23)

The SessionTransport abstract class is exported from voice/index.ts (line 21), and its abstract method signatures now reference IncomingMessage (line 106 of remote_session.ts) and SendMessageOptions (line 104). However, neither type is included in the re-export list at agents/src/voice/index.ts:14-23. External consumers who subclass SessionTransport or call RoomSessionTransport.sendMessage with options cannot import these types by name from @livekit/agents. The CLAUDE.md notes "API Extractor: Public API surface is tracked" — API Extractor would flag these as ae-forgotten-export warnings.

View 3 additional findings in Devin Review.

Open in Devin Review

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.

3 participants