Agent state management issues in `useAgent` hook
### 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:
1. **Agent disconnections mid-conversation don't trigger `failed` state** -
Agents that disconnect after successful connection don't set failure reasons
2. **`listening` state can occur with no agent participant** - State shows
`'listening'` when `agent.internal.agentParticipant` is `null`
---
## 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.state` should be `'failed'`
- `agent.failureReasons` should be set
**Actual:**
- `agent.state` remains 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
```typescript
// 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 = null
```
---
## Bug 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:**
```typescript
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`:
```typescript
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
```shell
```
### System Info
```shell
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_
关闭于 2025-11-17 7 条评论