> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stateset.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Native Widget Live Chat

> Use the terminal inbox to claim onsite conversations, send human replies, save private notes, and return control to AI.

**Outcome:** a signed visitor conversation receives a persisted operator reply through the standalone widget backend.**Prerequisites:** a backend with native live chat enabled, shared Redis for multiple replicas, and a separate tenant-scoped operator key per person.

This is the `response-chat-widget` server's native terminal workflow. The hosted ResponseCX
browser desk has a [separate setup](https://docs.stateset.com/stateset-response/chat-desk-widget.md). Native takeover also
does not synchronize replies into an external helpdesk ticket.

## Try the local playground

From the widget repository, with Node 20.20.0 and built browser assets:

```bash theme={null}
npm run tui:demo
```

Open the printed loopback customer URL. Select its demo conversation, press **t** to take over,
then **i** to write a reply. Send a visitor message in the browser and confirm it reaches the
console. The playground uses memory and a synthetic assistant; it does not call a provider,
customer database or helpdesk. `npm run tui -- --demo --once` prints a non-interactive snapshot.

## Connect to your API

```bash theme={null}
export WIDGET_ADMIN_URL=https://chat.example.com
export WIDGET_ADMIN_KEY=rw_admin_your_operator_key
export WIDGET_OPERATOR_NAME=Alex
npm run tui
```

Remote connections require HTTPS; loopback HTTP is accepted for development. The CLI does
not load `.env` automatically. Keep the key in the operator environment, outside storefront
config and source control. See [site administration](https://docs.stateset.com/stateset-response/response-chat-widget-admin.md)
for schema prerequisites and first-key bootstrap.

On the backend enable `WIDGET_LIVE_CHAT_ENABLED=true`. Visitors need valid signed widget
tokens so new threads have verified ownership. Ownerless legacy threads are excluded from
the inbox; caller-supplied tenant metadata cannot adopt them. Set `threadPollIntervalMs: 1500`
on the visitor widget, or use the session-enabled embed which supplies that default.

## Claim, reply and return

1. Select a conversation and press **t** to take over. Only the operator key that owns the
   claim can send new replies, renew, release or resolve it.
2. Send a visitor message. While takeover is active, the server persists it and returns
   `{ liveChat: { accepted: true, status: "active", operatorName }, reply: "" }`, including
   when streaming was requested. The widget adds no artificial assistant reply.
3. Press **i**, write a human reply and press Enter to send. The server acknowledges it only
   after thread persistence. Confirm the visitor receives it on a subsequent thread poll.
4. Press **a** to return control to AI, or **x** to resolve and release the conversation.
   A new visitor question after resolution can resume AI.

Claims last two minutes and renew every 30 seconds while the console is connected. A lost
connection can let the claim expire; the next visitor turn can resume AI and another operator
can claim. Customer polls run while the widget is open; the terminal refreshes every three
seconds. This transport is polling. A `thread_busy` response means a shared customer/AI/operator
write is finishing; wait and refresh before trying again.

**Verify:** one visitor message is stored once, the owning operator's reply
appears once, and AI stays paused until release or claim expiry.

## Inbox and private working state

| Control | Action |
| - | - |
| Enter | Open the selected conversation |
| `f` / Tab | Cycle All, Unread, AI, Live, Resolved, Mine and Waiting queues |
| `/` | Local search in the current view |
| `v` | Switch between customer chat and private notes |
| `o` in conversation | Compose a private team note |
| Ctrl+N while composing | Insert a line break |
| Escape while composing | Keep the draft and leave the composer |
| `s` in conversation / Ctrl+R in composer | Select a saved reply; Enter inserts it for review |
| `?` | Current keyboard help |

Drafts, local unread markers and searches last for the console session. Saved replies insert
text for review; insertion does not send. The inbox scans at most the latest 500 active threads
before tenant filtering. It is a bounded work queue, not a historical search archive.

Private notes have separate routes and never enter customer transcripts or model context.
Any operator in the owning organization can save a note without taking over. Each note uses
`clientNoteId`, is limited to 4000 characters, and the thread holds at most 100 notes/64,000
characters. The terminal displays incoming attachment labels and sends text replies.

## API clients and uncertain delivery

[Native operator routes](https://docs.stateset.com/api-reference/chat-widget/live-chat/api-admin-live-chat-list.md) use
`Authorization: Bearer rw_admin_...`, tenant ownership checks, `Cache-Control: no-store`, and
120 requests/minute per credential. Mutations share the same distributed thread lease as AI
and customer writes.

For a reply, supply `{ text, clientMessageId }` with a stable 8–128 character word/hyphen ID;
text is limited to 8000 characters. Repeating the stored ID with the same trimmed text and
operator replays the persisted result, even if the claim has since been released. Changed
content or operator returns `message_id_conflict`. Notes use the corresponding
`clientNoteId`/`note_id_conflict` contract. Preserve the receipt ID while reconciling a timeout;
using a new ID can duplicate an accepted message or note.

Before enabling this across customer traffic, run the staged checks in
[deployment](https://docs.stateset.com/stateset-response/response-chat-widget-deployment.md) on every replica.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.