-
Couldn't load subscription status.
- Fork 130
Description
Select which package(s) are affected
@livekit/components-react
Describe the bug
Hi @1egoman, I'm playing around with Agent SDK changes (from #1207) and observed some issues with state tracking/reporting in useAgent (Overall I like the changes though).
Summary
The useAgent hook has two bugs that cause incorrect state reporting when
agents disconnect or are missing:
- Agent disconnections mid-conversation don't trigger
failedstate -
Agents that disconnect after successful connection don't set failure reasons listeningstate can occur with no agent participant - State shows
'listening'whenagent.internal.agentParticipantisnull
Bug 1: Missing failure state on agent disconnection
Problem
When an agent successfully connects initially but then disconnects
mid-conversation, the state doesn't transition to failed and no failure
reasons are provided.
Expected vs Actual
Expected:
agent.stateshould be'failed'agent.failureReasonsshould be set
Actual:
agent.stateremains as'listening'- No failure reasons are set
Root Cause
It seems the failure detection in useAgentTimeoutIdStore (lines 192-204) only
works during initial connection timeout. Once the agent successfully connects,
the timeout completes and there's no mechanism to detect subsequent
disconnections.
File: components-js/packages/react/src/hooks/useAgent.ts
- Lines 192-204: Timeout logic
- Lines 436-446: Timeout lifecycle
Reproduction
// Agent process is started.
await session.start();
// Agent connects and works fine
// ...
// Kill the agent process
// Expected: state = 'failed', failureReasons = ['Agent disconnected']
// Actual: state = 'connecting' (or previous), failureReasons = nullBug 2: Inconsistent listening state
Problem
agent.state can be 'listening' while agent.internal.agentParticipant is
null, indicating the agent is listening when no agent actually exists.
Expected vs Actual
Expected:
- State reflects actual agent presence
'listening'state should only occur when an agent participant exists
Actual:
agent.state === "listening";
agent.internal.agentParticipant === null; // Inconsistent!Root Cause
It seems the state calculation (lines 394-425) sets state to 'listening' based
solely on localMicTrack:
if (localMicTrack) {
state = "listening";
bufferingSpeachLocally = true;
}This happens when the local microphone is published (buffering audio) but no
agent has joined the room yet.
File: components-js/packages/react/src/hooks/useAgent.ts
- Lines 408-411: State calculation
- Line 453: Agent participant in internal state
Reproduction
See above.
Logs
System Info
System:
OS: macOS 15.5
CPU: (10) arm64 Apple M4
Memory: 65.66 MB / 16.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 24.2.0 - /opt/homebrew/bin/node
npm: 11.4.2 - /opt/homebrew/bin/npm
pnpm: 10.18.3 - /opt/homebrew/bin/pnpm
Watchman: 2025.05.26.00 - /opt/homebrew/bin/watchman
Browsers:
Chrome: 141.0.7390.123
Firefox: 143.0.1
Safari: 18.5
npmPackages:
@livekit/components-core: file:vendor/livekit-components-core-0.12.10.tgz => 0.12.10
@livekit/components-react: file:vendor/livekit-components-react-2.9.15.tgz => 2.9.15
@livekit/components-styles: ^1.1.6 => 1.1.6
Using packed packages from f118da6e678c4a91be91c4dfc9b3b61eb7f64e2a (#1207)Severity
blocking an upgrade
Additional Information
No response