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 settingle.agent.recentlyClosedConversationDays, a plainnumberbetween 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 aconversations/updateupsert withconversation.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
startrange 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.stagewill beOPEN, 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/ringevents,{ conversationId, skillId }) are a sub-state of In-Queue, not a separate filter — answer withclient.dispatch({ action: 'conversation/accept', value: { conversationId } })orclient.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 currentASSIGNED_AGENT, moving a conversation back to In-Queue without changingstage.
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.timeis a plainnumber(nevernull) with two sentinel values:0means the conversation is closed and the SLA no longer applies,-1means the SLA time is unknown/unset. Any other value is an epoch-ms deadline —sla.time > 0filters out both sentinels in one check. -
conversation.sla.typeis'manual' | 'auto'— an agent-set manual SLA (viaconversation/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 intoconversation.sla.timebefore it reaches the SDK. Don't recompute the deadline from them; useconversation.sla.timedirectly.
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.timeis a plainnumber(nevernull):-1means 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. -
configurationabove issession.permissions.configuration(AgentConfiguration, from types.ts) — read it once per session, not per conversation. -
Per-skill override —
configuration.smartCapacityPerSkill: parsed from site settingmessaging.smart.capacity.per.skill.definition. Shape:{ smartCapacityParametersList: { skillId: number; smartCapacityFreshAgentPendingTimeSec: number }[] }. Look up the conversation'sskillIdin that list; if present, itssmartCapacityFreshAgentPendingTimeSecis the Time-to-Idle for that skill, in seconds. -
Global default —
configuration.smartCapacityDefaultSec: parsed from site settingmessaging.smart.capacity.fresh.agent.pending.time.sec. Used whenever no per-skill override matches the conversation'sskillId(including when the conversation has noskillId). - 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.definitionis missing or malformed JSON, the SDK falls back to an emptysmartCapacityParametersList(i.e. every conversation uses the global default) rather than throwing.
⚠️ Note: don't confuse this with
configuration.conversationIdleTimeMinutes(site settingle.agent.messaging.conversationIdleTime.minutes) — that's a separate, currently-unused-by-this-filter setting. The Idle filter's Time-to-Idle is derived solely fromsmartCapacityPerSkill/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);
}