**COMING SOON — BETA RELEASE NOT FOR PRODUCTION USE**

The Headless SDK is currently under active development and is not yet available for beta use. We’ve published this documentation early to help you familiarize yourself with the SDK and prepare for the upcoming beta release.

Once the Headless SDK is available for beta, we’ll notify brands through our release notes.

When released as beta:

  • The beta release is intended for early evaluation purposes only and is not recommended for production use.
  • The SDK may contain bugs, errors, or incomplete or inaccurate information.
  • APIs, features, and behavior are subject to change as we continue to develop and improve the SDK.
  • Do not deploy or rely on the beta release in production environments.

Please note: The documentation, APIs, features, and examples may change before and during the beta period.

Reproduces the Agent Workspace's conversation-list filters (Ongoing, In-Queue, Overdue, Closed, Idle) on top of @liveperson/headless-sdk. The SDK has no filters API. Each filter is a predicate your app runs over a locally-maintained conversation store.

Clients: all examples use only the public MessagingClient API — client.on({ callback }) for events and client.dispatch({ action, value }) for actions. dispatch() returns a Promise; inline references below omit await for brevity, but always await it in real code.

Prerequisite: live conversation store

What it does: Keeps a local, up-to-date map of the agent's open and recently-closed conversations. Every real-time filter below (Ongoing, In-Queue, Overdue, Idle, Closed) is computed by running a predicate over this map — none of them are separate SDK queries.

Instructions:

const conversations = new Map<string, Conversation>();

client.on({
  callback: (event) => {
    if (event.type === 'conversations/update') {
      const { type, conversation } = event.value;
      if (type === 'delete') {
        conversations.delete(conversation.id);
      } else {
        conversations.set(conversation.id, conversation);
      }
    }
  },
});

Build the map from conversations/update events after connect. MessagingClient exposes no snapshot API, so the store is populated entirely from the live event stream.

⚠️ Note: the recently-closed retention window is exposed as session.permissions.configuration.recentlyClosedConversationDays (site setting le.agent.recentlyClosedConversationDays, a plain number between 2–14). The SDK does not clamp, validate, or enforce it — it's a passthrough of whatever the site setting contains, so you'll need to enforce the time range yourself by not saving/showing conversations older than the configured retention.

Filter: Closed

What it does: Conversations that are resolved and marked closed (by agent, consumer, or system timeout), including post-survey. Agent Workspace shows conversations closed in the last 2 days by default, admin-configurable up to 14

Instructions:

  function isClosed(conversation) {
    return conversation.stage === 'CLOSE';
  }
  • Live close, or reacting to a close: client.dispatch({ action: 'conversation/close', value: conversationId }). would trigger a conversations/update upsert with conversation.stage === 'CLOSE'. Dispatching close on an already-closed conversation still sends the frame (there's no cached-state short-circuit), so dedupe in-app if you can close from multiple UI paths.
  • A start range wider than the account's configured retention returns nothing past the boundary and throws no error, so validate the range client-side if you want to warn the user.

⚠️ Note: if the conversation has an open post-survey dialog, conversation.stage will be OPEN, so you'll need to add this case to the condition to include it in this filter.

Filter: In-Queue

What it does: Open conversations waiting to be routed, not currently assigned to any agent.

Instructions:

function isInQueue(conversation) {
  return conversation.stage === 'OPEN' &&
    !conversation.mainDialog.participants.some((p) => p.role === 'ASSIGNED_AGENT');
}
  • Rings (conversation/ring events, { conversationId, skillId }) are a sub-state of In-Queue, not a separate filter — answer with client.dispatch({ action: 'conversation/accept', value: { conversationId } }) or client.dispatch({ action: 'conversation/reject', value: conversationId }). A rejected/expired ring leaves the conversation In-Queue for the next offer.
  • client.dispatch({ action: 'conversation/to-queue', value: conversationId }) removes the current ASSIGNED_AGENT, moving a conversation back to In-Queue without changing stage.

Filter: Overdue

What it does: Open, assigned conversations where the agent has breached the SLA response time.

Instructions:

  function isOverdue(conversation: Conversation): boolean {
    return conversation.stage === 'OPEN' &&
      conversation.sla.time > 0 &&
        conversation.sla.time < Date.now();
  }
  • conversation.sla.time is a plain number (never null) with two sentinel values: 0 means the conversation is closed and the SLA no longer applies, -1 means the SLA time is unknown/unset. Any other value is an epoch-ms deadline — sla.time > 0 filters out both sentinels in one check.
  • conversation.sla.type is 'manual' | 'auto' — an agent-set manual SLA (via conversation/set-manual-sla) still counts as Overdue once its deadline passes; only the Idle filter below excludes 'manual'.
  • The response-time targets used to compute this deadline server-side (session.permissions.configuration.defaultResponseTime / .urgentResponseTime / .prioritizedResponseTime, each { value: number; unit: string }) are informational only — the backend already factors them into conversation.sla.time before it reaches the SDK. Don't recompute the deadline from them; use conversation.sla.time directly.

Filter: Idle

What it does: Open, assigned conversations where the agent sent the last message but the consumer hasn't responded within the configured "Time-to-Idle." Lets agents filter out stale conversations.

Instructions:

function getTimeToIdleMs(skillId: number | undefined, configuration: AgentConfiguration): number {
  const perSkill = configuration.smartCapacityPerSkill.smartCapacityParametersList.find(
    (override) => override.skillId === skillId,
  );
  const timeToIdleSec = perSkill?.smartCapacityFreshAgentPendingTimeSec ?? configuration.smartCapacityDefaultSec;
  return timeToIdleSec * 1000;
}

function isIdle(conversation: Conversation, configuration: AgentConfiguration): boolean {
  const timeToIdleMs = getTimeToIdleMs(conversation.skillId, configuration);
  const lastMessage = conversation.lastMessage;
  let lastAgentMessageTime;
  if (lastMessage?.originatorRole === 'ASSIGNED_AGENT' || lastMessage?.originatorRole === 'MANAGER') {
      lastAgentMessageTime = lastMessage?.serverTimestamp;
  } else {
      const lastAgentMessage = [...conversation.mainDialog.messages]
          .reverse()
          .find((m) => m.participant.role === 'AGENT' || m.participant.role === 'ASSIGNED_AGENT');
      lastAgentMessageTime = lastAgentMessage?.sentAt;
  }
  
  return conversation.sla.type !== 'manual' &&
  conversation.sla.time === -1 &&
  (!lastAgentMessageTime || Date.now() - lastAgentMessageTime >= timeToIdleMs)
}
  • conversation.sla.time is a plain number (never null): -1 means no SLA countdown is active, which is the precondition for a conversation to be eligible as Idle instead of Overdue. 0 (closed) is excluded implicitly — Idle only applies to open, assigned conversations.
  • configuration above is session.permissions.configuration (AgentConfiguration, from types.ts) — read it once per session, not per conversation.
  • Per-skill override — configuration.smartCapacityPerSkill: parsed from site setting messaging.smart.capacity.per.skill.definition. Shape: { smartCapacityParametersList: { skillId: number; smartCapacityFreshAgentPendingTimeSec: number }[] }. Look up the conversation's skillId in that list; if present, its smartCapacityFreshAgentPendingTimeSec is the Time-to-Idle for that skill, in seconds.
  • Global default — configuration.smartCapacityDefaultSec: parsed from site setting messaging.smart.capacity.fresh.agent.pending.time.sec. Used whenever no per-skill override matches the conversation's skillId (including when the conversation has no skillId).
  • Precedence: per-skill override wins over the default. Both values are in seconds — multiply by 1000 before comparing against Date.now() deltas.
  • If messaging.smart.capacity.per.skill.definition is missing or malformed JSON, the SDK falls back to an empty smartCapacityParametersList (i.e. every conversation uses the global default) rather than throwing.

⚠️ Note: don't confuse this with configuration.conversationIdleTimeMinutes (site setting le.agent.messaging.conversationIdleTime.minutes) — that's a separate, currently-unused-by-this-filter setting. The Idle filter's Time-to-Idle is derived solely from smartCapacityPerSkill / smartCapacityDefaultSec.

Filter: Ongoing

What it does: Open, assigned conversations that are neither Overdue nor Idle — i.e., the last message was from the consumer, or from the agent within the configured idle time. Part of the default view; cannot be hidden.

Instructions:

function isOngoing(conversation: Conversation): boolean {
  return no-other-filter-applies;
}