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, configuration: AgentConfiguration): boolean {
  return !isClosed(conversation) && !isInQueue(conversation) && !isOverdue(conversation) && !isIdle(conversation, configuration);
}