@webex/contact-center 3.12.0-next.2 → 3.12.0-next.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/cc.js +132 -0
  2. package/dist/cc.js.map +1 -1
  3. package/dist/constants.js +2 -0
  4. package/dist/constants.js.map +1 -1
  5. package/dist/metrics/behavioral-events.js +26 -0
  6. package/dist/metrics/behavioral-events.js.map +1 -1
  7. package/dist/metrics/constants.js +4 -0
  8. package/dist/metrics/constants.js.map +1 -1
  9. package/dist/services/config/constants.js +1 -1
  10. package/dist/services/config/constants.js.map +1 -1
  11. package/dist/services/config/types.js +4 -0
  12. package/dist/services/config/types.js.map +1 -1
  13. package/dist/services/core/Err.js.map +1 -1
  14. package/dist/services/task/TaskManager.js +90 -8
  15. package/dist/services/task/TaskManager.js.map +1 -1
  16. package/dist/services/task/constants.js +3 -1
  17. package/dist/services/task/constants.js.map +1 -1
  18. package/dist/services/task/dialer.js +78 -0
  19. package/dist/services/task/dialer.js.map +1 -1
  20. package/dist/services/task/index.js +7 -2
  21. package/dist/services/task/index.js.map +1 -1
  22. package/dist/services/task/types.js +44 -0
  23. package/dist/services/task/types.js.map +1 -1
  24. package/dist/types/cc.d.ts +44 -0
  25. package/dist/types/constants.d.ts +2 -0
  26. package/dist/types/metrics/constants.d.ts +4 -0
  27. package/dist/types/services/config/types.d.ts +8 -0
  28. package/dist/types/services/core/Err.d.ts +4 -0
  29. package/dist/types/services/task/constants.d.ts +2 -0
  30. package/dist/types/services/task/dialer.d.ts +30 -0
  31. package/dist/types/services/task/types.d.ts +53 -1
  32. package/dist/webex.js +1 -1
  33. package/package.json +8 -8
  34. package/src/cc.ts +160 -0
  35. package/src/constants.ts +2 -0
  36. package/src/metrics/behavioral-events.ts +28 -0
  37. package/src/metrics/constants.ts +4 -0
  38. package/src/services/config/constants.ts +1 -1
  39. package/src/services/config/types.ts +4 -0
  40. package/src/services/core/Err.ts +2 -0
  41. package/src/services/task/TaskManager.ts +102 -22
  42. package/src/services/task/constants.ts +2 -0
  43. package/src/services/task/dialer.ts +80 -0
  44. package/src/services/task/index.ts +7 -2
  45. package/src/services/task/types.ts +56 -0
  46. package/test/unit/spec/cc.ts +130 -0
  47. package/test/unit/spec/services/config/index.ts +1 -1
  48. package/test/unit/spec/services/task/TaskManager.ts +238 -7
  49. package/test/unit/spec/services/task/dialer.ts +190 -0
  50. package/test/unit/spec/services/task/index.ts +21 -0
  51. package/umd/contact-center.min.js +2 -2
  52. package/umd/contact-center.min.js.map +1 -1
package/src/cc.ts CHANGED
@@ -1656,6 +1656,166 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1656
1656
  }
1657
1657
  }
1658
1658
 
1659
+ /**
1660
+ * Skips a campaign preview contact, requesting the next contact from the campaign.
1661
+ *
1662
+ * When a campaign manager reserves a contact for an agent, the agent receives an
1663
+ * `AgentOfferCampaignReservation` event. Instead of accepting, the agent can skip the
1664
+ * preview contact to move to the next contact in the campaign.
1665
+ *
1666
+ * @param {PreviewContactPayload} payload - The preview contact payload containing interactionId and campaignId (campaign name, not UUID).
1667
+ * @returns {Promise<TaskResponse>} Promise resolving with agent contact on success.
1668
+ * @throws {Error} If the operation fails (network error, etc.)
1669
+ *
1670
+ * @example
1671
+ * ```typescript
1672
+ * webex.cc.on('task:campaignPreviewReservation', async (task) => {
1673
+ * const { interactionId } = task.data;
1674
+ * const campaignId = task.data.interaction.callProcessingDetails.campaignId;
1675
+ *
1676
+ * const result = await webex.cc.skipPreviewContact({ interactionId, campaignId });
1677
+ * });
1678
+ * ```
1679
+ */
1680
+ public async skipPreviewContact(payload: PreviewContactPayload): Promise<TaskResponse> {
1681
+ const task = this.taskManager.getTask(payload.interactionId);
1682
+ if (task?.data?.interaction?.callProcessingDetails?.campaignPreviewSkipDisabled === 'true') {
1683
+ LoggerProxy.warn('Skip action is disabled for this campaign preview contact', {
1684
+ module: CC_FILE,
1685
+ method: METHODS.SKIP_PREVIEW_CONTACT,
1686
+ interactionId: payload.interactionId,
1687
+ });
1688
+ throw new Error('Skip action is disabled for this campaign preview contact');
1689
+ }
1690
+
1691
+ LoggerProxy.info('Skipping campaign preview contact', {
1692
+ module: CC_FILE,
1693
+ method: METHODS.SKIP_PREVIEW_CONTACT,
1694
+ });
1695
+ try {
1696
+ this.metricsManager.timeEvent([
1697
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_SUCCESS,
1698
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_FAILED,
1699
+ ]);
1700
+
1701
+ const result = await this.services.dialer.skipPreviewContact({data: payload});
1702
+
1703
+ this.metricsManager.trackEvent(
1704
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_SUCCESS,
1705
+ {
1706
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(result),
1707
+ interactionId: payload.interactionId,
1708
+ campaignId: payload.campaignId,
1709
+ },
1710
+ ['behavioral', 'business', 'operational']
1711
+ );
1712
+
1713
+ LoggerProxy.log('Campaign preview contact skipped successfully', {
1714
+ module: CC_FILE,
1715
+ method: METHODS.SKIP_PREVIEW_CONTACT,
1716
+ trackingId: result.trackingId,
1717
+ interactionId: payload.interactionId,
1718
+ });
1719
+
1720
+ return result;
1721
+ } catch (error) {
1722
+ const failure = error.details as Failure;
1723
+ this.metricsManager.trackEvent(
1724
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_FAILED,
1725
+ {
1726
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failure),
1727
+ interactionId: payload.interactionId,
1728
+ campaignId: payload.campaignId,
1729
+ },
1730
+ ['behavioral', 'business', 'operational']
1731
+ );
1732
+ const {error: detailedError} = getErrorDetails(error, METHODS.SKIP_PREVIEW_CONTACT, CC_FILE);
1733
+ throw detailedError;
1734
+ }
1735
+ }
1736
+
1737
+ /**
1738
+ * Removes a campaign preview contact from the campaign list entirely.
1739
+ *
1740
+ * When a campaign manager reserves a contact for an agent, the agent receives an
1741
+ * `AgentOfferCampaignReservation` event. Instead of accepting or skipping, the agent can
1742
+ * remove the preview contact to permanently take it out of the campaign contact list.
1743
+ *
1744
+ * @param {PreviewContactPayload} payload - The preview contact payload containing interactionId and campaignId (campaign name, not UUID).
1745
+ * @returns {Promise<TaskResponse>} Promise resolving with agent contact on success.
1746
+ * @throws {Error} If the operation fails (network error, etc.)
1747
+ *
1748
+ * @example
1749
+ * ```typescript
1750
+ * webex.cc.on('task:campaignPreviewReservation', async (task) => {
1751
+ * const { interactionId } = task.data;
1752
+ * const campaignId = task.data.interaction.callProcessingDetails.campaignId;
1753
+ *
1754
+ * const result = await webex.cc.removePreviewContact({ interactionId, campaignId });
1755
+ * });
1756
+ * ```
1757
+ */
1758
+ public async removePreviewContact(payload: PreviewContactPayload): Promise<TaskResponse> {
1759
+ const task = this.taskManager.getTask(payload.interactionId);
1760
+ if (task?.data?.interaction?.callProcessingDetails?.campaignPreviewRemoveDisabled === 'true') {
1761
+ LoggerProxy.warn('Remove action is disabled for this campaign preview contact', {
1762
+ module: CC_FILE,
1763
+ method: METHODS.REMOVE_PREVIEW_CONTACT,
1764
+ interactionId: payload.interactionId,
1765
+ });
1766
+ throw new Error('Remove action is disabled for this campaign preview contact');
1767
+ }
1768
+
1769
+ LoggerProxy.info('Removing campaign preview contact', {
1770
+ module: CC_FILE,
1771
+ method: METHODS.REMOVE_PREVIEW_CONTACT,
1772
+ });
1773
+ try {
1774
+ this.metricsManager.timeEvent([
1775
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_SUCCESS,
1776
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_FAILED,
1777
+ ]);
1778
+
1779
+ const result = await this.services.dialer.removePreviewContact({data: payload});
1780
+
1781
+ this.metricsManager.trackEvent(
1782
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_SUCCESS,
1783
+ {
1784
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(result),
1785
+ interactionId: payload.interactionId,
1786
+ campaignId: payload.campaignId,
1787
+ },
1788
+ ['behavioral', 'business', 'operational']
1789
+ );
1790
+
1791
+ LoggerProxy.log('Campaign preview contact removed successfully', {
1792
+ module: CC_FILE,
1793
+ method: METHODS.REMOVE_PREVIEW_CONTACT,
1794
+ trackingId: result.trackingId,
1795
+ interactionId: payload.interactionId,
1796
+ });
1797
+
1798
+ return result;
1799
+ } catch (error) {
1800
+ const failure = error.details as Failure;
1801
+ this.metricsManager.trackEvent(
1802
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_FAILED,
1803
+ {
1804
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failure),
1805
+ interactionId: payload.interactionId,
1806
+ campaignId: payload.campaignId,
1807
+ },
1808
+ ['behavioral', 'business', 'operational']
1809
+ );
1810
+ const {error: detailedError} = getErrorDetails(
1811
+ error,
1812
+ METHODS.REMOVE_PREVIEW_CONTACT,
1813
+ CC_FILE
1814
+ );
1815
+ throw detailedError;
1816
+ }
1817
+ }
1818
+
1659
1819
  /**
1660
1820
  * Fetches outdial ANI (Automatic Number Identification) entries for an outdial ANI ID.
1661
1821
  *
package/src/constants.ts CHANGED
@@ -50,6 +50,8 @@ export const METHODS = {
50
50
  HANDLE_TASK_HYDRATE: 'handleTaskHydrate',
51
51
  INCOMING_TASK_LISTENER: 'incomingTaskListener',
52
52
  ACCEPT_PREVIEW_CONTACT: 'acceptPreviewContact',
53
+ SKIP_PREVIEW_CONTACT: 'skipPreviewContact',
54
+ REMOVE_PREVIEW_CONTACT: 'removePreviewContact',
53
55
  GET_BASE_URL: 'getBaseUrl',
54
56
  SEND_EVENT: 'sendEvent',
55
57
  FETCH_HISTORIC_TRANSCRIPTS: 'fetchHistoricTranscripts',
@@ -449,6 +449,34 @@ const eventTaxonomyMap: Record<string, BehavioralEventTaxonomy> = {
449
449
  target: 'campaign_preview_accept',
450
450
  verb: 'fail',
451
451
  },
452
+
453
+ // Campaign Preview Skip API Events
454
+ [METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_SUCCESS]: {
455
+ product,
456
+ agent: 'user',
457
+ target: 'campaign_preview_skip',
458
+ verb: 'complete',
459
+ },
460
+ [METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_FAILED]: {
461
+ product,
462
+ agent: 'user',
463
+ target: 'campaign_preview_skip',
464
+ verb: 'fail',
465
+ },
466
+
467
+ // Campaign Preview Remove API Events
468
+ [METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_SUCCESS]: {
469
+ product,
470
+ agent: 'user',
471
+ target: 'campaign_preview_remove',
472
+ verb: 'complete',
473
+ },
474
+ [METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_FAILED]: {
475
+ product,
476
+ agent: 'user',
477
+ target: 'campaign_preview_remove',
478
+ verb: 'fail',
479
+ },
452
480
  };
453
481
 
454
482
  /**
@@ -163,6 +163,10 @@ export const METRIC_EVENT_NAMES = {
163
163
  // Campaign Preview API Events
164
164
  CAMPAIGN_PREVIEW_ACCEPT_SUCCESS: 'Campaign Preview Accept Success',
165
165
  CAMPAIGN_PREVIEW_ACCEPT_FAILED: 'Campaign Preview Accept Failed',
166
+ CAMPAIGN_PREVIEW_SKIP_SUCCESS: 'Campaign Preview Skip Success',
167
+ CAMPAIGN_PREVIEW_SKIP_FAILED: 'Campaign Preview Skip Failed',
168
+ CAMPAIGN_PREVIEW_REMOVE_SUCCESS: 'Campaign Preview Remove Success',
169
+ CAMPAIGN_PREVIEW_REMOVE_FAILED: 'Campaign Preview Remove Failed',
166
170
 
167
171
  // AI Assistant transcript events
168
172
  AI_ASSISTANT_SEND_EVENT_SUCCESS: 'AI Assistant Send Event Success',
@@ -166,7 +166,7 @@ export const endPointMap = {
166
166
  ) =>
167
167
  `organization/${orgId}/v2/auxiliary-code?page=${page}&pageSize=${pageSize}${
168
168
  filter && filter.length > 0 ? `&filter=id=in=(${filter})` : ''
169
- }&attributes=${attributes}`,
169
+ }&attributes=${attributes}&desktopProfileFilter=true`,
170
170
 
171
171
  /**
172
172
  * Gets the endpoint for organization info.
@@ -117,6 +117,10 @@ export const CC_TASK_EVENTS = {
117
117
  CAMPAIGN_CONTACT_UPDATED: 'CampaignContactUpdated',
118
118
  /** Event emitted when accepting a campaign preview contact fails */
119
119
  CAMPAIGN_PREVIEW_ACCEPT_FAILED: 'CampaignPreviewAcceptFailed',
120
+ /** Event emitted when skipping a campaign preview contact fails */
121
+ CAMPAIGN_PREVIEW_SKIP_FAILED: 'CampaignPreviewSkipFailed',
122
+ /** Event emitted when removing a campaign preview contact fails */
123
+ CAMPAIGN_PREVIEW_REMOVE_FAILED: 'CampaignPreviewRemoveFailed',
120
124
  /** Event emitted when a real-time transcript chunk is received */
121
125
  REAL_TIME_TRANSCRIPTION: 'REAL_TIME_TRANSCRIPTION',
122
126
  } as const;
@@ -39,6 +39,8 @@ export type TaskErrorIds =
39
39
  | {'Service.aqm.task.resumeRecording': Failure}
40
40
  | {'Service.aqm.dialer.startOutdial': Failure}
41
41
  | {'Service.aqm.dialer.acceptPreviewContact': Failure}
42
+ | {'Service.aqm.dialer.skipPreviewContact': Failure}
43
+ | {'Service.aqm.dialer.removePreviewContact': Failure}
42
44
  | {'Service.reqs.generic.failure': {trackingId: string}};
43
45
 
44
46
  export type ReqError =
@@ -312,14 +312,11 @@ export default class TaskManager extends EventEmitter {
312
312
  case CC_EVENTS.AGENT_CONTACT_OFFER_RONA:
313
313
  case CC_EVENTS.AGENT_CONTACT_ASSIGN_FAILED:
314
314
  case CC_EVENTS.AGENT_INVITE_FAILED: {
315
- LoggerProxy.warn(
316
- `[DEBUG-CAMPAIGN-CLEAR] Task removal triggered by ${payload.data.type}, interactionId=${payload.data.interactionId}, taskType=${task?.data?.type}`,
317
- {
318
- module: TASK_MANAGER_FILE,
319
- method: METHODS.REGISTER_TASK_LISTENERS,
320
- interactionId: payload.data.interactionId,
321
- }
322
- );
315
+ LoggerProxy.info(`Task removal triggered by ${payload.data.type}`, {
316
+ module: TASK_MANAGER_FILE,
317
+ method: METHODS.REGISTER_TASK_LISTENERS,
318
+ interactionId: payload.data.interactionId,
319
+ });
323
320
  task = this.updateTaskData(task, payload.data);
324
321
 
325
322
  const eventTypeToMetricMap: Record<string, keyof typeof METRIC_EVENT_NAMES> = {
@@ -343,19 +340,31 @@ export default class TaskManager extends EventEmitter {
343
340
  break;
344
341
  }
345
342
  case CC_EVENTS.CONTACT_ENDED:
346
- // Update task data
343
+ // Update task data.
347
344
  if (task) {
348
- LoggerProxy.warn(
349
- `[DEBUG-CAMPAIGN-CLEAR] CONTACT_ENDED, interactionId=${payload.data.interactionId}, taskType=${task?.data?.type}, state=${task?.data?.interaction?.state}`,
350
- {
351
- module: TASK_MANAGER_FILE,
352
- method: METHODS.REGISTER_TASK_LISTENERS,
353
- interactionId: payload.data.interactionId,
354
- }
345
+ LoggerProxy.info(`Contact ended for interaction`, {
346
+ module: TASK_MANAGER_FILE,
347
+ method: METHODS.REGISTER_TASK_LISTENERS,
348
+ interactionId: payload.data.interactionId,
349
+ });
350
+
351
+ // Campaign preview tasks should never trigger wrapup on ContactEnded —
352
+ // they are terminal cleanup events. For all other tasks, derive
353
+ // wrapUpRequired from agentsPendingWrapUp as before.
354
+ const CAMPAIGN_OUTBOUND_TYPES = [
355
+ 'STANDARD_PREVIEW_CAMPAIGN',
356
+ 'DIRECT_PREVIEW_CAMPAIGN',
357
+ ];
358
+ const isCampaignPreview = CAMPAIGN_OUTBOUND_TYPES.includes(
359
+ task.data?.interaction?.outboundType ?? ''
355
360
  );
361
+ const wrapUpRequired = isCampaignPreview
362
+ ? false
363
+ : payload.data.agentsPendingWrapUp?.includes(this.agentId) || false;
364
+
356
365
  task = this.updateTaskData(task, {
357
366
  ...payload.data,
358
- wrapUpRequired: payload.data.agentsPendingWrapUp?.includes(this.agentId) || false,
367
+ wrapUpRequired,
359
368
  });
360
369
 
361
370
  // Handle cleanup based on whether task should be deleted
@@ -364,12 +373,83 @@ export default class TaskManager extends EventEmitter {
364
373
  task?.emit(TASK_EVENTS.TASK_END, task);
365
374
  }
366
375
  break;
367
- case CC_EVENTS.CAMPAIGN_CONTACT_UPDATED:
368
- // CampaignContactUpdated is a non-terminal event (intermediate update during accept).
369
- // Only update the task data do NOT remove the task or emit TASK_END.
370
- // Task cleanup is handled by CONTACT_ENDED or other terminal events.
376
+ case CC_EVENTS.CAMPAIGN_CONTACT_UPDATED: {
377
+ // CampaignContactUpdated is a non-terminal event (e.g., next contact after skip/remove).
378
+ // Update the task data and emit an event so consumers can react to the updated contact.
379
+ // Do NOT remove the task or emit TASK_END — cleanup is handled by CONTACT_ENDED.
371
380
  if (task) {
372
- task = this.updateTaskData(task, payload.data);
381
+ // Carry forward campaign preview fields from existing task data since the updated
382
+ // contact payload may not include them, and reconcileData would delete them.
383
+ const existingCpd = task.data?.interaction?.callProcessingDetails;
384
+ const updatedData = {...payload.data};
385
+
386
+ if (existingCpd) {
387
+ const campaignFields = {
388
+ ...(existingCpd.campaignPreviewAutoAction && {
389
+ campaignPreviewAutoAction: existingCpd.campaignPreviewAutoAction,
390
+ }),
391
+ ...(existingCpd.campaignPreviewOfferTimeout && {
392
+ campaignPreviewOfferTimeout: existingCpd.campaignPreviewOfferTimeout,
393
+ }),
394
+ ...(existingCpd.campaignPreviewSkipDisabled && {
395
+ campaignPreviewSkipDisabled: existingCpd.campaignPreviewSkipDisabled,
396
+ }),
397
+ ...(existingCpd.campaignPreviewRemoveDisabled && {
398
+ campaignPreviewRemoveDisabled: existingCpd.campaignPreviewRemoveDisabled,
399
+ }),
400
+ };
401
+
402
+ if (!updatedData.interaction) {
403
+ updatedData.interaction = {} as typeof updatedData.interaction;
404
+ }
405
+
406
+ updatedData.interaction = {
407
+ ...updatedData.interaction,
408
+ callProcessingDetails: {
409
+ ...campaignFields,
410
+ ...(updatedData.interaction.callProcessingDetails || {}),
411
+ } as typeof existingCpd,
412
+ };
413
+ }
414
+
415
+ LoggerProxy.log('Campaign contact updated - carrying forward preview fields', {
416
+ module: TASK_MANAGER_FILE,
417
+ method: METHODS.REGISTER_TASK_LISTENERS,
418
+ interactionId: payload.data.interactionId,
419
+ data: {
420
+ hasCpd: !!updatedData.interaction?.callProcessingDetails,
421
+ autoAction:
422
+ updatedData.interaction?.callProcessingDetails?.campaignPreviewAutoAction,
423
+ skipDisabled:
424
+ updatedData.interaction?.callProcessingDetails?.campaignPreviewSkipDisabled,
425
+ removeDisabled:
426
+ updatedData.interaction?.callProcessingDetails?.campaignPreviewRemoveDisabled,
427
+ },
428
+ });
429
+
430
+ task = this.updateTaskData(task, updatedData);
431
+ task.emit(TASK_EVENTS.TASK_CAMPAIGN_CONTACT_UPDATED, task);
432
+ }
433
+ break;
434
+ }
435
+ case CC_EVENTS.CAMPAIGN_PREVIEW_ACCEPT_FAILED:
436
+ if (task) {
437
+ // Failure payloads are sparse (no interaction field). Spread existing
438
+ // task data first so reconcileData doesn't delete interaction/cpd.
439
+ task = this.updateTaskData(task, {...task.data, ...payload.data});
440
+ task.emit(TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_ACCEPT_FAILED, task);
441
+ }
442
+ break;
443
+ case CC_EVENTS.CAMPAIGN_PREVIEW_SKIP_FAILED:
444
+ if (task) {
445
+ task = this.updateTaskData(task, {...task.data, ...payload.data});
446
+ task.emit(TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_SKIP_FAILED, task);
447
+ }
448
+ break;
449
+ case CC_EVENTS.CAMPAIGN_PREVIEW_REMOVE_FAILED:
450
+ if (task) {
451
+ task = this.updateTaskData(task, {...task.data, ...payload.data});
452
+ task.emit(TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_REMOVE_FAILED, task);
373
453
  }
374
454
  break;
375
455
  case CC_EVENTS.CONTACT_MERGED:
@@ -24,6 +24,8 @@ export const CONFERENCE_EXIT = '/conference/exit';
24
24
  export const CONFERENCE_TRANSFER = '/conference/transfer';
25
25
  export const DIALER_API = '/v1/dialer';
26
26
  export const CAMPAIGN_PREVIEW_ACCEPT = '/accept';
27
+ export const CAMPAIGN_PREVIEW_SKIP = '/skip';
28
+ export const CAMPAIGN_PREVIEW_REMOVE = '/remove';
27
29
  /** 80-second timeout for accepting preview contact (outbound call setup takes longer than default 20s) */
28
30
  export const TIMEOUT_PREVIEW_ACCEPT = 80000;
29
31
  export const TASK_MANAGER_FILE = 'taskManager';
@@ -7,6 +7,8 @@ import {
7
7
  TASK_API,
8
8
  DIALER_API,
9
9
  CAMPAIGN_PREVIEW_ACCEPT,
10
+ CAMPAIGN_PREVIEW_SKIP,
11
+ CAMPAIGN_PREVIEW_REMOVE,
10
12
  TIMEOUT_PREVIEW_ACCEPT,
11
13
  } from './constants';
12
14
  import * as Contact from './types';
@@ -103,5 +105,83 @@ export default function aqmDialer(aqm: AqmReqs) {
103
105
  errId: 'Service.aqm.dialer.acceptPreviewContact',
104
106
  },
105
107
  })),
108
+
109
+ /**
110
+ * Skips a campaign preview contact, requesting the next contact from the campaign.
111
+ *
112
+ * @param {Object} p - Parameters object.
113
+ * @param {Contact.PreviewContactPayload} p.data - Payload containing interactionId and campaignId.
114
+ * @returns {Promise<Contact.AgentContact>} A promise that resolves with agent contact on success.
115
+ *
116
+ * Emits:
117
+ * - `CC_EVENTS.CAMPAIGN_CONTACT_UPDATED` or `CC_EVENTS.CONTACT_ENDED` on success
118
+ * - `CC_EVENTS.CAMPAIGN_PREVIEW_SKIP_FAILED` on failure
119
+ * @ignore
120
+ */
121
+ skipPreviewContact: aqm.req((p: {data: Contact.PreviewContactPayload}) => ({
122
+ url: `${DIALER_API}/campaign/${encodeURIComponent(p.data.campaignId)}/preview-task/${
123
+ p.data.interactionId
124
+ }${CAMPAIGN_PREVIEW_SKIP}`,
125
+ host: WCC_API_GATEWAY,
126
+ data: {},
127
+ method: HTTP_METHODS.POST,
128
+ err,
129
+ notifSuccess: {
130
+ bind: {
131
+ type: TASK_MESSAGE_TYPE,
132
+ data: {
133
+ type: [CC_EVENTS.CAMPAIGN_CONTACT_UPDATED, CC_EVENTS.CONTACT_ENDED],
134
+ interactionId: p.data.interactionId,
135
+ },
136
+ },
137
+ msg: {} as Contact.AgentContact,
138
+ },
139
+ notifFail: {
140
+ bind: {
141
+ type: TASK_MESSAGE_TYPE,
142
+ data: {type: CC_EVENTS.CAMPAIGN_PREVIEW_SKIP_FAILED, campaignId: p.data.campaignId},
143
+ },
144
+ errId: 'Service.aqm.dialer.skipPreviewContact',
145
+ },
146
+ })),
147
+
148
+ /**
149
+ * Removes a campaign preview contact from the campaign list entirely.
150
+ *
151
+ * @param {Object} p - Parameters object.
152
+ * @param {Contact.PreviewContactPayload} p.data - Payload containing interactionId and campaignId.
153
+ * @returns {Promise<Contact.AgentContact>} A promise that resolves with agent contact on success.
154
+ *
155
+ * Emits:
156
+ * - `CC_EVENTS.CAMPAIGN_CONTACT_UPDATED` or `CC_EVENTS.CONTACT_ENDED` on success
157
+ * - `CC_EVENTS.CAMPAIGN_PREVIEW_REMOVE_FAILED` on failure
158
+ * @ignore
159
+ */
160
+ removePreviewContact: aqm.req((p: {data: Contact.PreviewContactPayload}) => ({
161
+ url: `${DIALER_API}/campaign/${encodeURIComponent(p.data.campaignId)}/preview-task/${
162
+ p.data.interactionId
163
+ }${CAMPAIGN_PREVIEW_REMOVE}`,
164
+ host: WCC_API_GATEWAY,
165
+ data: {},
166
+ method: HTTP_METHODS.POST,
167
+ err,
168
+ notifSuccess: {
169
+ bind: {
170
+ type: TASK_MESSAGE_TYPE,
171
+ data: {
172
+ type: [CC_EVENTS.CAMPAIGN_CONTACT_UPDATED, CC_EVENTS.CONTACT_ENDED],
173
+ interactionId: p.data.interactionId,
174
+ },
175
+ },
176
+ msg: {} as Contact.AgentContact,
177
+ },
178
+ notifFail: {
179
+ bind: {
180
+ type: TASK_MESSAGE_TYPE,
181
+ data: {type: CC_EVENTS.CAMPAIGN_PREVIEW_REMOVE_FAILED, campaignId: p.data.campaignId},
182
+ },
183
+ errId: 'Service.aqm.dialer.removePreviewContact',
184
+ },
185
+ })),
106
186
  };
107
187
  }
@@ -565,7 +565,10 @@ export default class Task extends EventEmitter implements ITask {
565
565
  METRIC_EVENT_NAMES.TASK_HOLD_FAILED,
566
566
  ]);
567
567
 
568
- const effectiveMediaResourceId = mediaResourceId ?? this.data.mediaResourceId;
568
+ const {mainInteractionId} = this.data.interaction;
569
+ const defaultMediaResourceId =
570
+ this.data.interaction.media[mainInteractionId]?.mediaResourceId;
571
+ const effectiveMediaResourceId = mediaResourceId ?? defaultMediaResourceId;
569
572
 
570
573
  const response = await this.contact.hold({
571
574
  interactionId: this.data.interactionId,
@@ -599,7 +602,9 @@ export default class Task extends EventEmitter implements ITask {
599
602
  errorData: err.data?.errorData,
600
603
  reasonCode: err.data?.reasonCode,
601
604
  };
602
- const effectiveMediaResourceId = mediaResourceId ?? this.data.mediaResourceId;
605
+ const defaultMediaResourceId =
606
+ this.data.interaction.media[this.data.interaction.mainInteractionId]?.mediaResourceId;
607
+ const effectiveMediaResourceId = mediaResourceId ?? defaultMediaResourceId;
603
608
 
604
609
  this.metricsManager.trackEvent(
605
610
  METRIC_EVENT_NAMES.TASK_HOLD_FAILED,
@@ -554,6 +554,54 @@ export enum TASK_EVENTS {
554
554
  * ```
555
555
  */
556
556
  TASK_CAMPAIGN_PREVIEW_RESERVATION = 'task:campaignPreviewReservation',
557
+
558
+ /**
559
+ * Triggered when accepting a campaign preview contact fails
560
+ * @example
561
+ * ```typescript
562
+ * task.on(TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_ACCEPT_FAILED, (task: ITask) => {
563
+ * console.log('Campaign preview accept failed:', task.data.interactionId);
564
+ * // Handle accept failure
565
+ * });
566
+ * ```
567
+ */
568
+ TASK_CAMPAIGN_PREVIEW_ACCEPT_FAILED = 'task:campaignPreviewAcceptFailed',
569
+
570
+ /**
571
+ * Triggered when skipping a campaign preview contact fails
572
+ * @example
573
+ * ```typescript
574
+ * task.on(TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_SKIP_FAILED, (task: ITask) => {
575
+ * console.log('Campaign preview skip failed:', task.data.interactionId);
576
+ * // Handle skip failure
577
+ * });
578
+ * ```
579
+ */
580
+ TASK_CAMPAIGN_PREVIEW_SKIP_FAILED = 'task:campaignPreviewSkipFailed',
581
+
582
+ /**
583
+ * Triggered when removing a campaign preview contact fails
584
+ * @example
585
+ * ```typescript
586
+ * task.on(TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_REMOVE_FAILED, (task: ITask) => {
587
+ * console.log('Campaign preview remove failed:', task.data.interactionId);
588
+ * // Handle remove failure
589
+ * });
590
+ * ```
591
+ */
592
+ TASK_CAMPAIGN_PREVIEW_REMOVE_FAILED = 'task:campaignPreviewRemoveFailed',
593
+
594
+ /**
595
+ * Triggered when a campaign contact is updated (e.g., after skip or remove, when the next contact is offered)
596
+ * @example
597
+ * ```typescript
598
+ * task.on(TASK_EVENTS.TASK_CAMPAIGN_CONTACT_UPDATED, (task: ITask) => {
599
+ * console.log('Campaign contact updated:', task.data.interactionId);
600
+ * // Handle updated campaign contact (e.g., display next contact)
601
+ * });
602
+ * ```
603
+ */
604
+ TASK_CAMPAIGN_CONTACT_UPDATED = 'task:campaignContactUpdated',
557
605
  }
558
606
 
559
607
  /**
@@ -702,6 +750,14 @@ export type Interaction = {
702
750
  fcDesktopView?: string;
703
751
  /** Agent ID who initiated the outdial call */
704
752
  outdialAgentId?: string;
753
+ /** Indicates if the skip action is disabled for campaign preview contacts */
754
+ campaignPreviewSkipDisabled?: string;
755
+ /** Indicates if the remove action is disabled for campaign preview contacts */
756
+ campaignPreviewRemoveDisabled?: string;
757
+ /** Auto-action to perform when campaign preview offer times out (ACCEPT, SKIP, REMOVE) */
758
+ campaignPreviewAutoAction?: string;
759
+ /** Timestamp (ms) when the campaign preview offer expires */
760
+ campaignPreviewOfferTimeout?: string;
705
761
  };
706
762
  /** Main interaction identifier for related interactions */
707
763
  mainInteractionId?: string;