@webex/contact-center 3.12.0-llmrefactor.2 → 3.12.0-llmrefactor.4

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 (187) hide show
  1. package/.sdd/manifest.json +5 -4
  2. package/ai-docs/ARCHITECTURE.md +1 -1
  3. package/ai-docs/CONTRACTS.md +6 -2
  4. package/ai-docs/SECURITY.md +1 -1
  5. package/ai-docs/contact-center-spec.md +22 -4
  6. package/ai-docs/features/consult-transfer-list-policy/spec/feature-spec.md +362 -0
  7. package/dist/cc.js +440 -10
  8. package/dist/cc.js.map +1 -1
  9. package/dist/config.js +7 -0
  10. package/dist/config.js.map +1 -1
  11. package/dist/constants.js +10 -2
  12. package/dist/constants.js.map +1 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/metrics/behavioral-events.js +13 -0
  15. package/dist/metrics/behavioral-events.js.map +1 -1
  16. package/dist/metrics/constants.js +2 -0
  17. package/dist/metrics/constants.js.map +1 -1
  18. package/dist/services/AddressBook.js +18 -15
  19. package/dist/services/AddressBook.js.map +1 -1
  20. package/dist/services/AnswerCallOnWebexService.js +174 -0
  21. package/dist/services/AnswerCallOnWebexService.js.map +1 -0
  22. package/dist/services/EntryPoint.js +46 -68
  23. package/dist/services/EntryPoint.js.map +1 -1
  24. package/dist/services/Queue.js +27 -22
  25. package/dist/services/Queue.js.map +1 -1
  26. package/dist/services/WebexCrossClientService.js +171 -0
  27. package/dist/services/WebexCrossClientService.js.map +1 -0
  28. package/dist/services/WxAppTelephonyMercurySync.js +93 -0
  29. package/dist/services/WxAppTelephonyMercurySync.js.map +1 -0
  30. package/dist/services/config/Util.js +3 -0
  31. package/dist/services/config/Util.js.map +1 -1
  32. package/dist/services/config/constants.js +10 -6
  33. package/dist/services/config/constants.js.map +1 -1
  34. package/dist/services/config/types.js +4 -0
  35. package/dist/services/config/types.js.map +1 -1
  36. package/dist/services/core/Err.js.map +1 -1
  37. package/dist/services/core/WebexRequest.js +6 -2
  38. package/dist/services/core/WebexRequest.js.map +1 -1
  39. package/dist/services/core/aqm-reqs.js +28 -14
  40. package/dist/services/core/aqm-reqs.js.map +1 -1
  41. package/dist/services/core/types.js.map +1 -1
  42. package/dist/services/task/Task.js +73 -7
  43. package/dist/services/task/Task.js.map +1 -1
  44. package/dist/services/task/TaskFactory.js +8 -4
  45. package/dist/services/task/TaskFactory.js.map +1 -1
  46. package/dist/services/task/TaskManager.js +353 -21
  47. package/dist/services/task/TaskManager.js.map +1 -1
  48. package/dist/services/task/WebexCallingUtils.js +70 -0
  49. package/dist/services/task/WebexCallingUtils.js.map +1 -0
  50. package/dist/services/task/constants.js +4 -1
  51. package/dist/services/task/constants.js.map +1 -1
  52. package/dist/services/task/contact.js +29 -0
  53. package/dist/services/task/contact.js.map +1 -1
  54. package/dist/services/task/digital/Digital.js +3 -2
  55. package/dist/services/task/digital/Digital.js.map +1 -1
  56. package/dist/services/task/state-machine/TaskStateMachine.js +135 -37
  57. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -1
  58. package/dist/services/task/state-machine/actions.js +6 -1
  59. package/dist/services/task/state-machine/actions.js.map +1 -1
  60. package/dist/services/task/state-machine/guards.js +38 -11
  61. package/dist/services/task/state-machine/guards.js.map +1 -1
  62. package/dist/services/task/state-machine/types.js.map +1 -1
  63. package/dist/services/task/state-machine/uiControlsComputer.js +118 -18
  64. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -1
  65. package/dist/services/task/types.js +16 -1
  66. package/dist/services/task/types.js.map +1 -1
  67. package/dist/services/task/voice/Voice.js +350 -14
  68. package/dist/services/task/voice/Voice.js.map +1 -1
  69. package/dist/services/task/voice/WebRTC.js +4 -1
  70. package/dist/services/task/voice/WebRTC.js.map +1 -1
  71. package/dist/services/task/voice/wxAppVoiceMethods.js +201 -0
  72. package/dist/services/task/voice/wxAppVoiceMethods.js.map +1 -0
  73. package/dist/services/wxAppTelephonyUtils.js +19 -0
  74. package/dist/services/wxAppTelephonyUtils.js.map +1 -0
  75. package/dist/types/cc.d.ts +77 -4
  76. package/dist/types/config.d.ts +7 -0
  77. package/dist/types/constants.d.ts +8 -0
  78. package/dist/types/index.d.ts +3 -1
  79. package/dist/types/metrics/constants.d.ts +2 -0
  80. package/dist/types/services/AddressBook.d.ts +2 -1
  81. package/dist/types/services/AnswerCallOnWebexService.d.ts +37 -0
  82. package/dist/types/services/EntryPoint.d.ts +7 -5
  83. package/dist/types/services/Queue.d.ts +5 -3
  84. package/dist/types/services/WebexCrossClientService.d.ts +28 -0
  85. package/dist/types/services/WxAppTelephonyMercurySync.d.ts +28 -0
  86. package/dist/types/services/config/constants.d.ts +8 -5
  87. package/dist/types/services/config/types.d.ts +17 -4
  88. package/dist/types/services/core/Err.d.ts +2 -0
  89. package/dist/types/services/core/WebexRequest.d.ts +1 -0
  90. package/dist/types/services/core/types.d.ts +2 -0
  91. package/dist/types/services/task/Task.d.ts +20 -2
  92. package/dist/types/services/task/TaskFactory.d.ts +2 -1
  93. package/dist/types/services/task/WebexCallingUtils.d.ts +11 -0
  94. package/dist/types/services/task/constants.d.ts +3 -0
  95. package/dist/types/services/task/contact.d.ts +4 -0
  96. package/dist/types/services/task/digital/Digital.d.ts +2 -2
  97. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +260 -56
  98. package/dist/types/services/task/state-machine/guards.d.ts +6 -5
  99. package/dist/types/services/task/state-machine/types.d.ts +9 -1
  100. package/dist/types/services/task/types.d.ts +81 -4
  101. package/dist/types/services/task/voice/Voice.d.ts +48 -9
  102. package/dist/types/services/task/voice/WebRTC.d.ts +2 -2
  103. package/dist/types/services/task/voice/wxAppVoiceMethods.d.ts +52 -0
  104. package/dist/types/services/wxAppTelephonyUtils.d.ts +5 -0
  105. package/dist/types/types.d.ts +14 -8
  106. package/dist/types/utils/PageCache.d.ts +20 -3
  107. package/dist/types.js +7 -2
  108. package/dist/types.js.map +1 -1
  109. package/dist/utils/PageCache.js +19 -5
  110. package/dist/utils/PageCache.js.map +1 -1
  111. package/dist/webex.js +1 -1
  112. package/package.json +9 -9
  113. package/src/cc.ts +506 -9
  114. package/src/config.ts +7 -0
  115. package/src/constants.ts +8 -0
  116. package/src/index.ts +2 -0
  117. package/src/metrics/ai-docs/metrics-spec.md +9 -3
  118. package/src/metrics/behavioral-events.ts +14 -0
  119. package/src/metrics/constants.ts +2 -0
  120. package/src/services/AddressBook.ts +17 -6
  121. package/src/services/AnswerCallOnWebexService.ts +206 -0
  122. package/src/services/EntryPoint.ts +59 -60
  123. package/src/services/Queue.ts +29 -12
  124. package/src/services/WebexCrossClientService.ts +212 -0
  125. package/src/services/WxAppTelephonyMercurySync.ts +115 -0
  126. package/src/services/ai-docs/AGENTS.md +10 -10
  127. package/src/services/ai-docs/services-spec.md +6 -1
  128. package/src/services/config/Util.ts +3 -0
  129. package/src/services/config/ai-docs/AGENTS.md +1 -1
  130. package/src/services/config/ai-docs/ARCHITECTURE.md +2 -2
  131. package/src/services/config/ai-docs/config-spec.md +6 -0
  132. package/src/services/config/constants.ts +10 -6
  133. package/src/services/config/types.ts +16 -4
  134. package/src/services/core/Err.ts +1 -0
  135. package/src/services/core/WebexRequest.ts +3 -1
  136. package/src/services/core/ai-docs/core-spec.md +5 -1
  137. package/src/services/core/aqm-reqs.ts +30 -15
  138. package/src/services/core/types.ts +2 -0
  139. package/src/services/task/Task.ts +79 -8
  140. package/src/services/task/TaskFactory.ts +8 -3
  141. package/src/services/task/TaskManager.ts +522 -16
  142. package/src/services/task/WebexCallingUtils.ts +136 -0
  143. package/src/services/task/ai-docs/ARCHITECTURE.md +1 -0
  144. package/src/services/task/ai-docs/task-spec.md +152 -2
  145. package/src/services/task/constants.ts +3 -0
  146. package/src/services/task/contact.ts +30 -0
  147. package/src/services/task/digital/Digital.ts +4 -2
  148. package/src/services/task/state-machine/TaskStateMachine.ts +210 -63
  149. package/src/services/task/state-machine/actions.ts +5 -2
  150. package/src/services/task/state-machine/ai-docs/AGENTS.md +8 -4
  151. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +17 -8
  152. package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +56 -24
  153. package/src/services/task/state-machine/guards.ts +64 -11
  154. package/src/services/task/state-machine/types.ts +16 -1
  155. package/src/services/task/state-machine/uiControlsComputer.ts +206 -32
  156. package/src/services/task/types.ts +114 -4
  157. package/src/services/task/voice/Voice.ts +462 -12
  158. package/src/services/task/voice/WebRTC.ts +5 -1
  159. package/src/services/task/voice/wxAppVoiceMethods.ts +307 -0
  160. package/src/services/wxAppTelephonyUtils.ts +14 -0
  161. package/src/types.ts +33 -9
  162. package/src/utils/AGENTS.md +21 -10
  163. package/src/utils/PageCache.ts +38 -5
  164. package/src/utils/ai-docs/utils-spec.md +21 -11
  165. package/test/unit/spec/cc.ts +1274 -0
  166. package/test/unit/spec/metrics/behavioral-events.ts +18 -0
  167. package/test/unit/spec/services/AddressBook.ts +37 -6
  168. package/test/unit/spec/services/AnswerCallOnWebexService.ts +223 -0
  169. package/test/unit/spec/services/EntryPoint.ts +87 -40
  170. package/test/unit/spec/services/Queue.ts +123 -12
  171. package/test/unit/spec/services/WebexCrossClientService.ts +261 -0
  172. package/test/unit/spec/services/WxAppTelephonyMercurySync.ts +113 -0
  173. package/test/unit/spec/services/config/Util.ts +85 -0
  174. package/test/unit/spec/services/core/WebexRequest.ts +3 -1
  175. package/test/unit/spec/services/core/aqm-reqs.ts +113 -1
  176. package/test/unit/spec/services/task/Task.ts +200 -0
  177. package/test/unit/spec/services/task/TaskFactory.ts +39 -2
  178. package/test/unit/spec/services/task/TaskManager.ts +1294 -1
  179. package/test/unit/spec/services/task/WebexCallingUtils.ts +153 -0
  180. package/test/unit/spec/services/task/contact.ts +33 -0
  181. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +854 -72
  182. package/test/unit/spec/services/task/state-machine/guards.ts +210 -8
  183. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +445 -7
  184. package/test/unit/spec/services/task/voice/Voice.ts +874 -0
  185. package/test/unit/spec/services/task/voice/wxAppVoiceMethods.ts +459 -0
  186. package/umd/contact-center.min.js +2 -2
  187. package/umd/contact-center.min.js.map +1 -1
@@ -240,6 +240,7 @@ All event names are defined in `METRIC_EVENT_NAMES` (`constants.ts`). Events fol
240
240
  | `TASK_CONFERENCE_END_SUCCESS` / `FAILED` | `'Task Conference End ...'` | Conference end result |
241
241
  | `TASK_CONFERENCE_TRANSFER_SUCCESS` / `FAILED` | `'Task Conference Transfer ...'` | Conference transfer result |
242
242
  | `TASK_CONFERENCE_EXIT_SUCCESS` / `FAILED` | `'Task Conference Exit ...'` | Conference exit result |
243
+ | `TASK_CONFERENCE_PARTICIPANT_DROP_SUCCESS` / `FAILED` | `'Task Conference Participant Drop ...'` | Participant Drop result |
243
244
  | `TASK_SWITCH_CALL_SUCCESS` / `FAILED` | `'Task Switch Call ...'` | Switch call result |
244
245
  | `WEBSOCKET_REGISTER_SUCCESS` / `FAILED` | `'Websocket Register ...'` | WebSocket registration result |
245
246
  | `WEBSOCKET_DEREGISTER_SUCCESS` / `FAIL` | `'Websocket Deregister ...'` | WebSocket deregistration result |
@@ -277,6 +278,7 @@ All event names are defined in `constants.ts` as `METRIC_EVENT_NAMES`. Events fo
277
278
  | Conference End | `TASK_CONFERENCE_END_SUCCESS` | `TASK_CONFERENCE_END_FAILED` |
278
279
  | Conference Transfer | `TASK_CONFERENCE_TRANSFER_SUCCESS` | `TASK_CONFERENCE_TRANSFER_FAILED` |
279
280
  | Conference Exit | `TASK_CONFERENCE_EXIT_SUCCESS` | `TASK_CONFERENCE_EXIT_FAILED` |
281
+ | Conference Participant Drop | `TASK_CONFERENCE_PARTICIPANT_DROP_SUCCESS` | `TASK_CONFERENCE_PARTICIPANT_DROP_FAILED` |
280
282
  | Switch Call | `TASK_SWITCH_CALL_SUCCESS` | `TASK_SWITCH_CALL_FAILED` |
281
283
  | Outdial | `TASK_OUTDIAL_SUCCESS` | `TASK_OUTDIAL_FAILED` |
282
284
  | Upload Logs | `UPLOAD_LOGS_SUCCESS` | `UPLOAD_LOGS_FAILED` |
@@ -297,11 +299,11 @@ Special events (no success/failure pair):
297
299
 
298
300
  - `WEBSOCKET_EVENT_RECEIVED` — **no** behavioral taxonomy (not in `eventTaxonomyMap`)
299
301
 
300
- Of the 80 defined metric names, 71 have behavioral taxonomy and 9 do not. Events **without** an `eventTaxonomyMap` entry are the six `AI_ASSISTANT_*` names plus `WEBSOCKET_DEREGISTER_SUCCESS`, `WEBSOCKET_DEREGISTER_FAIL`, and `WEBSOCKET_EVENT_RECEIVED`.
302
+ Of the 82 defined metric names, 73 have behavioral taxonomy and 9 do not. Events **without** an `eventTaxonomyMap` entry are the six `AI_ASSISTANT_*` names plus `WEBSOCKET_DEREGISTER_SUCCESS`, `WEBSOCKET_DEREGISTER_FAIL`, and `WEBSOCKET_EVENT_RECEIVED`.
301
303
 
302
304
  ### Complete METRIC_EVENT_NAMES catalog
303
305
 
304
- This table contains all 80 names from `src/metrics/constants.ts`; taxonomy presence is checked against `src/metrics/behavioral-events.ts`: 71 mapped and 9 unmapped.
306
+ This table contains all 82 names from `src/metrics/constants.ts`; taxonomy presence is checked against `src/metrics/behavioral-events.ts`: 73 mapped and 9 unmapped.
305
307
 
306
308
  | Constant | Emitted name | Behavioral taxonomy? |
307
309
  |---|---|---|
@@ -354,6 +356,8 @@ This table contains all 80 names from `src/metrics/constants.ts`; taxonomy prese
354
356
  | `TASK_CONFERENCE_TRANSFER_FAILED` | `Task Conference Transfer Failed` | yes |
355
357
  | `TASK_CONFERENCE_EXIT_SUCCESS` | `Task Conference Exit Success` | yes |
356
358
  | `TASK_CONFERENCE_EXIT_FAILED` | `Task Conference Exit Failed` | yes |
359
+ | `TASK_CONFERENCE_PARTICIPANT_DROP_SUCCESS` | `Task Conference Participant Drop Success` | yes |
360
+ | `TASK_CONFERENCE_PARTICIPANT_DROP_FAILED` | `Task Conference Participant Drop Failed` | yes |
357
361
  | `TASK_SWITCH_CALL_SUCCESS` | `Task Switch Call Success` | yes |
358
362
  | `TASK_SWITCH_CALL_FAILED` | `Task Switch Call Failed` | yes |
359
363
  | `TASK_OUTDIAL_SUCCESS` | `Task Outdial Success` | yes |
@@ -463,6 +467,8 @@ Example: `wxcc_sdk.user.station_login.complete`
463
467
 
464
468
  The mapping is defined in `behavioral-events.ts` via `eventTaxonomyMap` and accessed through `getEventTaxonomy(name)`.
465
469
 
470
+ Participant Drop uses target `task_conference_participant_drop` with `complete` for success and `fail` for failure. Its metric payload is limited to task/request correlation and standard AQM tracking fields; participant IDs, numbers, names, URLs, headers, and raw routing payloads are excluded.
471
+
466
472
  ```mermaid
467
473
  flowchart LR
468
474
  A[timeEvent keys] --> B[runningEvents stores startTime + key Set]
@@ -728,7 +734,7 @@ MetricsManager holds three pending queues, running timing entries, a readiness f
728
734
 
729
735
  ## Business Rules & Invariants
730
736
  - `timeEvent` and every tracking method return immediately while metrics are disabled.
731
- - Only names present in `eventTaxonomyMap` receive behavioral taxonomy; 71 of 80 names are mapped and the six AI Assistant plus three WebSocket names are intentionally catalogued as unmapped.
737
+ - Only names present in `eventTaxonomyMap` receive behavioral taxonomy; 73 of 82 names are mapped and the six AI Assistant plus three WebSocket names are intentionally catalogued as unmapped.
732
738
  - `submittingEvents` prevents overlapping queue-drain executions.
733
739
  - Authentication is inherited from the host Webex SDK's metrics client; MetricsManager owns no credentials, tokens, or authorization policy.
734
740
 
@@ -366,6 +366,20 @@ const eventTaxonomyMap: Record<string, BehavioralEventTaxonomy> = {
366
366
  verb: 'fail',
367
367
  },
368
368
 
369
+ // Conference Participant Drop
370
+ [METRIC_EVENT_NAMES.TASK_CONFERENCE_PARTICIPANT_DROP_SUCCESS]: {
371
+ product,
372
+ agent: 'user',
373
+ target: 'task_conference_participant_drop',
374
+ verb: 'complete',
375
+ },
376
+ [METRIC_EVENT_NAMES.TASK_CONFERENCE_PARTICIPANT_DROP_FAILED]: {
377
+ product,
378
+ agent: 'user',
379
+ target: 'task_conference_participant_drop',
380
+ verb: 'fail',
381
+ },
382
+
369
383
  // Switch Call
370
384
  [METRIC_EVENT_NAMES.TASK_SWITCH_CALL_SUCCESS]: {
371
385
  product,
@@ -131,6 +131,8 @@ export const METRIC_EVENT_NAMES = {
131
131
  TASK_CONFERENCE_TRANSFER_FAILED: 'Task Conference Transfer Failed',
132
132
  TASK_CONFERENCE_EXIT_SUCCESS: 'Task Conference Exit Success',
133
133
  TASK_CONFERENCE_EXIT_FAILED: 'Task Conference Exit Failed',
134
+ TASK_CONFERENCE_PARTICIPANT_DROP_SUCCESS: 'Task Conference Participant Drop Success',
135
+ TASK_CONFERENCE_PARTICIPANT_DROP_FAILED: 'Task Conference Participant Drop Failed',
134
136
  TASK_SWITCH_CALL_SUCCESS: 'Task Switch Call Success',
135
137
  TASK_SWITCH_CALL_FAILED: 'Task Switch Call Failed',
136
138
 
@@ -74,7 +74,8 @@ export class AddressBook {
74
74
 
75
75
  /**
76
76
  * Fetches address book entries for a specific address book using the entry API
77
- * @param {AddressBookEntrySearchParams} [params] - Search and pagination parameters including addressBookId
77
+ * @param {AddressBookEntrySearchParams} [params] - Search, pagination, and optional sort parameters
78
+ * including addressBookId. Sorting defaults to name ascending.
78
79
  * @returns {Promise<AddressBookEntriesResponse>} Promise resolving to address book entries
79
80
  * @throws {Error} If the API call fails
80
81
  * @public
@@ -102,12 +103,21 @@ export class AddressBook {
102
103
  search,
103
104
  filter,
104
105
  attributes,
106
+ sortBy = 'name',
107
+ sortOrder = 'asc',
105
108
  } = params;
106
109
 
107
110
  // Use provided addressBookId or fall back to agent's address book
108
111
  const bookId = addressBookId || this.getAddressBookId();
109
112
  const orgId = this.webex.credentials.getOrgId();
110
- const isSearchRequest = !!(search || filter || attributes);
113
+ const hasCustomSort = sortBy !== 'name' || sortOrder !== 'asc';
114
+ const isSearchRequest = !!(search || filter || attributes || hasCustomSort);
115
+ const canUseCache = this.pageCache.canUseCache({
116
+ search,
117
+ filter,
118
+ attributes,
119
+ sortBy: hasCustomSort ? sortBy : undefined,
120
+ });
111
121
 
112
122
  LoggerProxy.info('Fetching address book entries', {
113
123
  module: 'AddressBook',
@@ -121,8 +131,8 @@ export class AddressBook {
121
131
  },
122
132
  });
123
133
 
124
- // Check if we can use cache for simple pagination (no search/filter/attributes)
125
- if (this.pageCache.canUseCache({search, filter, attributes})) {
134
+ // Default name-ascending pagination is cache-compatible; result-changing overrides are not.
135
+ if (canUseCache) {
126
136
  const cacheKey = this.pageCache.buildCacheKey(bookId, page, pageSize);
127
137
  const cachedPage = this.pageCache.getCachedPage(cacheKey);
128
138
 
@@ -190,6 +200,7 @@ export class AddressBook {
190
200
  if (filter) queryParams.append('filter', filter);
191
201
  if (attributes) queryParams.append('attributes', attributes);
192
202
  if (search) queryParams.append('search', search);
203
+ if (sortBy) queryParams.append('sort', `${sortBy},${sortOrder.toUpperCase()}`);
193
204
 
194
205
  const resource = endPointMap.addressBookEntries(orgId, bookId, queryParams.toString());
195
206
 
@@ -244,8 +255,8 @@ export class AddressBook {
244
255
  );
245
256
  }
246
257
 
247
- // Cache the page data for simple pagination (no search/filter/attributes)
248
- if (this.pageCache.canUseCache({search, filter, attributes}) && response.body?.data) {
258
+ // Cache only the invariant default-order pagination shape.
259
+ if (canUseCache && response.body?.data) {
249
260
  const cacheKey = this.pageCache.buildCacheKey(bookId, page, pageSize);
250
261
  this.pageCache.cachePage(cacheKey, response.body.data, response.body.meta);
251
262
 
@@ -0,0 +1,206 @@
1
+ import LoggerProxy from '../logger-proxy';
2
+ import {CC_FILE, METHODS} from '../constants';
3
+ import {HTTP_METHODS, IHttpResponse, WebexSDK} from '../types';
4
+ import {getErrorDetails} from './core/Utils';
5
+ import {isWxAppCallNotFoundError} from './wxAppTelephonyUtils';
6
+
7
+ const ANSWER_CALL_ON_WEBEX_FILE = 'AnswerCallOnWebexService';
8
+
9
+ export type WxAppTelephonyError = Error & {
10
+ isWxAppTelephonyError: true;
11
+ trackingId?: string;
12
+ status?: number | string;
13
+ };
14
+
15
+ const markWxAppTelephonyError = (error: Error, source: unknown): WxAppTelephonyError => {
16
+ const marked = error as WxAppTelephonyError;
17
+ marked.isWxAppTelephonyError = true;
18
+ const sourceObj = source as {
19
+ details?: {trackingId?: string; status?: number | string};
20
+ statusCode?: number;
21
+ status?: number | string;
22
+ };
23
+ const trackingId = sourceObj?.details?.trackingId;
24
+ if (trackingId) {
25
+ marked.trackingId = trackingId;
26
+ }
27
+ const status = sourceObj?.statusCode ?? sourceObj?.details?.status ?? sourceObj?.status;
28
+ if (status !== undefined) {
29
+ marked.status = status;
30
+ }
31
+
32
+ return marked;
33
+ };
34
+
35
+ export type AnswerCallParams = {
36
+ callId: string;
37
+ endpointId: string;
38
+ lineOwnerId?: string;
39
+ };
40
+
41
+ export type CallIdParams = {
42
+ callId: string;
43
+ lineOwnerId?: string;
44
+ };
45
+
46
+ export type TransmitDtmfParams = CallIdParams & {
47
+ dtmf: string;
48
+ };
49
+
50
+ export type CallDetailsResponse = {
51
+ muted?: boolean;
52
+ };
53
+
54
+ /**
55
+ * REST telephony client for wxApp thick-client answer (WXCC-6026).
56
+ * Separate from WebCallingService (WebRTC/Mobius).
57
+ */
58
+ export default class AnswerCallOnWebexService {
59
+ private webex: WebexSDK;
60
+
61
+ constructor(webex: WebexSDK) {
62
+ this.webex = webex;
63
+ }
64
+
65
+ private getTelephonyBaseUrl(): string {
66
+ const services = this.webex.internal.services;
67
+ const hydraUrl = services._serviceUrls?.hydra || services.get('hydra');
68
+
69
+ if (!hydraUrl) {
70
+ throw new Error('Telephony base URL is unavailable');
71
+ }
72
+
73
+ return `${hydraUrl.replace(/\/$/, '')}/telephony/calls`;
74
+ }
75
+
76
+ private async telephonyRequest(
77
+ resource: string,
78
+ method: HTTP_METHODS,
79
+ body: Record<string, unknown>,
80
+ logMethod: string
81
+ ): Promise<IHttpResponse> {
82
+ try {
83
+ const uri = `${this.getTelephonyBaseUrl()}${resource}`;
84
+
85
+ LoggerProxy.info(`AnswerCallOnWebexService.${logMethod} request`, {
86
+ module: ANSWER_CALL_ON_WEBEX_FILE,
87
+ method: logMethod,
88
+ data: {uri, bodyKeys: Object.keys(body)},
89
+ });
90
+
91
+ return (await this.webex.request({
92
+ uri,
93
+ method,
94
+ body,
95
+ addAuthHeader: true,
96
+ })) as IHttpResponse;
97
+ } catch (error) {
98
+ LoggerProxy.error(`AnswerCallOnWebexService.${logMethod} failed: ${error}`, {
99
+ module: ANSWER_CALL_ON_WEBEX_FILE,
100
+ method: logMethod,
101
+ });
102
+ const {error: detailedError} = getErrorDetails(error, logMethod, CC_FILE);
103
+ throw markWxAppTelephonyError(detailedError, error);
104
+ }
105
+ }
106
+
107
+ public answerCall(params: AnswerCallParams): Promise<IHttpResponse> {
108
+ const {callId, endpointId, lineOwnerId} = params;
109
+
110
+ return this.telephonyRequest(
111
+ '/answer',
112
+ HTTP_METHODS.POST,
113
+ {
114
+ callId,
115
+ endpointId,
116
+ ...(lineOwnerId ? {lineOwnerId} : {}),
117
+ },
118
+ METHODS.ACCEPT
119
+ );
120
+ }
121
+
122
+ public rejectCall(params: CallIdParams): Promise<IHttpResponse> {
123
+ const {callId, lineOwnerId} = params;
124
+
125
+ return this.telephonyRequest(
126
+ '/reject',
127
+ HTTP_METHODS.POST,
128
+ {callId, ...(lineOwnerId ? {lineOwnerId} : {})},
129
+ METHODS.REJECT
130
+ );
131
+ }
132
+
133
+ public muteCall(params: CallIdParams): Promise<IHttpResponse> {
134
+ const {callId, lineOwnerId} = params;
135
+
136
+ return this.telephonyRequest(
137
+ '/mute',
138
+ HTTP_METHODS.POST,
139
+ {callId, ...(lineOwnerId ? {lineOwnerId} : {})},
140
+ METHODS.TOGGLE_MUTE
141
+ );
142
+ }
143
+
144
+ public unmuteCall(params: CallIdParams): Promise<IHttpResponse> {
145
+ const {callId, lineOwnerId} = params;
146
+
147
+ return this.telephonyRequest(
148
+ '/unmute',
149
+ HTTP_METHODS.POST,
150
+ {callId, ...(lineOwnerId ? {lineOwnerId} : {})},
151
+ METHODS.TOGGLE_MUTE
152
+ );
153
+ }
154
+
155
+ public transmitDtmf(params: TransmitDtmfParams): Promise<IHttpResponse> {
156
+ const {callId, dtmf, lineOwnerId} = params;
157
+
158
+ return this.telephonyRequest(
159
+ '/transmitDtmf',
160
+ HTTP_METHODS.POST,
161
+ {callId, dtmf, ...(lineOwnerId ? {lineOwnerId} : {})},
162
+ METHODS.TRANSMIT_DTMF
163
+ );
164
+ }
165
+
166
+ public async getCallDetails(params: CallIdParams): Promise<CallDetailsResponse> {
167
+ const {callId, lineOwnerId} = params;
168
+ const lineOwnerIdQuery = lineOwnerId ? `?lineOwnerId=${encodeURIComponent(lineOwnerId)}` : '';
169
+ const uri = `${this.getTelephonyBaseUrl()}/${callId}${lineOwnerIdQuery}`;
170
+
171
+ try {
172
+ LoggerProxy.info('AnswerCallOnWebexService.getCallDetails request', {
173
+ module: ANSWER_CALL_ON_WEBEX_FILE,
174
+ method: METHODS.GET_CALL_DETAILS_ON_WEBEX,
175
+ data: {callId},
176
+ });
177
+
178
+ const response = (await this.webex.request({
179
+ uri,
180
+ method: HTTP_METHODS.GET,
181
+ addAuthHeader: true,
182
+ })) as IHttpResponse;
183
+
184
+ return (response.body ?? {}) as CallDetailsResponse;
185
+ } catch (error) {
186
+ if (isWxAppCallNotFoundError(error)) {
187
+ const err =
188
+ error instanceof Error
189
+ ? error
190
+ : new Error(String((error as {message?: string})?.message ?? error));
191
+ throw markWxAppTelephonyError(err, error);
192
+ }
193
+
194
+ LoggerProxy.error(`AnswerCallOnWebexService.getCallDetails failed: ${error}`, {
195
+ module: ANSWER_CALL_ON_WEBEX_FILE,
196
+ method: METHODS.GET_CALL_DETAILS_ON_WEBEX,
197
+ });
198
+ const {error: detailedError} = getErrorDetails(
199
+ error,
200
+ METHODS.GET_CALL_DETAILS_ON_WEBEX,
201
+ CC_FILE
202
+ );
203
+ throw markWxAppTelephonyError(detailedError, error);
204
+ }
205
+ }
206
+ }
@@ -2,12 +2,40 @@ import {HTTP_METHODS, WebexSDK} from '../types';
2
2
  import type {EntryPointRecord, EntryPointListResponse, EntryPointSearchParams} from '../types';
3
3
  import LoggerProxy from '../logger-proxy';
4
4
  import WebexRequest from './core/WebexRequest';
5
- import PageCache, {PAGINATION_DEFAULTS} from '../utils/PageCache';
5
+ import {PAGINATION_DEFAULTS} from '../utils/PageCache';
6
6
  import MetricsManager from '../metrics/MetricsManager';
7
7
  import {WCC_API_GATEWAY} from './constants';
8
8
  import {endPointMap} from './config/constants';
9
9
  import {METRIC_EVENT_NAMES} from '../metrics/constants';
10
10
 
11
+ const DEFAULT_ENTRY_POINT_ATTRIBUTES = 'id,dialledNumber,entryPointId,entryPointName';
12
+ const DEFAULT_ENTRY_POINT_SORT_FIELD = 'entryPointName';
13
+
14
+ type DialNumberEntryPointRecord = {
15
+ id: string;
16
+ dialledNumber?: string;
17
+ entryPointId: string;
18
+ entryPointName: string;
19
+ };
20
+
21
+ const mergeEntryPointAttributes = (attributes?: string): string => {
22
+ const mergedAttributes = new Set(DEFAULT_ENTRY_POINT_ATTRIBUTES.split(','));
23
+
24
+ attributes
25
+ ?.split(',')
26
+ .map((attribute) => attribute.trim())
27
+ .filter(Boolean)
28
+ .forEach((attribute) => mergedAttributes.add(attribute));
29
+
30
+ return Array.from(mergedAttributes).join(',');
31
+ };
32
+
33
+ const mapDialNumberEntryPoint = (item: DialNumberEntryPointRecord): EntryPointRecord => ({
34
+ id: item.entryPointId,
35
+ name: item.entryPointName,
36
+ ...(item.dialledNumber ? {number: item.dialledNumber} : {}),
37
+ });
38
+
11
39
  /**
12
40
  * EntryPoint class for managing Webex Contact Center entry points.
13
41
  * Provides functionality to fetch, search, and paginate through entry points.
@@ -35,7 +63,7 @@ import {METRIC_EVENT_NAMES} from '../metrics/constants';
35
63
  * });
36
64
  *
37
65
  * // Search for specific entry points
38
- * const searchResults = await entryPointAPI.searchEntryPoints({
66
+ * const searchResults = await entryPointAPI.getEntryPoints({
39
67
  * search: 'support',
40
68
  * filter: 'type=="voice"'
41
69
  * });
@@ -46,9 +74,6 @@ export class EntryPoint {
46
74
  private webex: WebexSDK;
47
75
  private metricsManager: MetricsManager;
48
76
 
49
- // Page cache using the common utility
50
- private pageCache: PageCache<EntryPointRecord>;
51
-
52
77
  /**
53
78
  * Creates an instance of EntryPoint
54
79
  * @param {WebexSDK} webex - The Webex SDK instance
@@ -57,19 +82,21 @@ export class EntryPoint {
57
82
  constructor(webex: WebexSDK) {
58
83
  this.webex = webex;
59
84
  this.webexRequest = WebexRequest.getInstance({webex});
60
- this.pageCache = new PageCache<EntryPointRecord>('EntryPoint');
61
85
  this.metricsManager = MetricsManager.getInstance({webex});
62
86
  }
63
87
 
64
88
  /**
65
- * Fetches entry points for the organization with pagination support
66
- * @param {EntryPointSearchParams} [params] - Search and pagination parameters
89
+ * Fetches entry-point dial-number mappings for the organization with pagination support. By
90
+ * default, returns the agent's desktop-profile-filtered mappings in backend entry-point-name order.
91
+ * Search, filter, attribute, and sort parameters can customize the request while desktop-profile
92
+ * scoping and required mapping fields remain SDK-owned.
93
+ * @param {EntryPointSearchParams} [params] - Search, pagination, and compatible override parameters.
67
94
  * @returns {Promise<EntryPointListResponse>} Promise resolving to paginated entry points
68
95
  * @throws {Error} If the API call fails
69
96
  * @public
70
97
  * @example
71
98
  * ```typescript
72
- * // Get first page of entry points
99
+ * // Get the first page using the default entry-point policy
73
100
  * const response = await entryPointAPI.getEntryPoints();
74
101
  *
75
102
  * // Get specific page with custom page size
@@ -89,12 +116,14 @@ export class EntryPoint {
89
116
  search,
90
117
  filter,
91
118
  attributes,
92
- sortBy,
119
+ sortBy = 'name',
93
120
  sortOrder = 'asc',
94
121
  } = params;
95
122
 
96
123
  const orgId = this.webex.credentials.getOrgId();
97
- const isSearchRequest = !!(search || filter || attributes || sortBy);
124
+ const hasCustomSort = sortBy !== 'name' || sortOrder !== 'asc';
125
+ const isSearchRequest = !!(search || filter || attributes || hasCustomSort);
126
+ const effectiveSortBy = sortBy === 'name' ? DEFAULT_ENTRY_POINT_SORT_FIELD : sortBy;
98
127
 
99
128
  LoggerProxy.info(
100
129
  `Fetching entry points - orgId: ${orgId}, page: ${page}, pageSize: ${pageSize}, isSearchRequest: ${isSearchRequest}`,
@@ -104,51 +133,24 @@ export class EntryPoint {
104
133
  }
105
134
  );
106
135
 
107
- // Check if we can use cache for simple pagination (no search/filter/attributes/sort)
108
- if (this.pageCache.canUseCache({search, filter, attributes, sortBy})) {
109
- const cacheKey = this.pageCache.buildCacheKey(orgId, page, pageSize);
110
- const cachedPage = this.pageCache.getCachedPage(cacheKey);
111
-
112
- if (cachedPage) {
113
- const duration = Date.now() - startTime;
114
-
115
- LoggerProxy.log(
116
- `Returning page ${page} from cache - cacheHit: true, duration: ${duration}ms, recordCount: ${cachedPage.data.length}, pageSize: ${pageSize}`,
117
- {
118
- module: 'EntryPoint',
119
- method: 'getEntryPoints',
120
- }
121
- );
122
-
123
- return {
124
- data: cachedPage.data,
125
- meta: {
126
- page,
127
- pageSize,
128
- totalPages: cachedPage.totalMeta?.totalPages,
129
- totalRecords: cachedPage.totalMeta?.totalRecords,
130
- },
131
- };
132
- }
133
- }
134
-
135
- // Start timing only for actual API calls (not cache hits)
136
136
  this.metricsManager.timeEvent(METRIC_EVENT_NAMES.ENTRYPOINT_FETCH_SUCCESS);
137
137
 
138
138
  try {
139
- // Build query parameters
140
139
  const queryParams = new URLSearchParams({
141
140
  page: page.toString(),
142
141
  pageSize: pageSize.toString(),
143
- sortOrder,
142
+ attributes: mergeEntryPointAttributes(attributes),
144
143
  });
145
144
 
146
145
  if (search) queryParams.append('search', search);
147
146
  if (filter) queryParams.append('filter', filter);
148
- if (attributes) queryParams.append('attributes', attributes);
149
- if (sortBy) queryParams.append('sortBy', sortBy);
147
+ if (effectiveSortBy) {
148
+ queryParams.append('sort', `${effectiveSortBy},${sortOrder.toUpperCase()}`);
149
+ }
150
+ queryParams.append('desktopProfileFilter', 'true');
151
+ queryParams.append('includeEntryPointName', 'true');
150
152
 
151
- const resource = endPointMap.entryPointList(orgId, queryParams.toString());
153
+ const resource = endPointMap.entryPointDialNumberList(orgId, queryParams.toString());
152
154
 
153
155
  LoggerProxy.log(
154
156
  `Making API request to fetch entry points - resource: ${resource}, service: ${WCC_API_GATEWAY}`,
@@ -162,12 +164,21 @@ export class EntryPoint {
162
164
  service: WCC_API_GATEWAY,
163
165
  resource,
164
166
  method: HTTP_METHODS.GET,
167
+ headers: {
168
+ 'X-ORGANIZATION-ID': orgId,
169
+ 'x-ignore-internal-data': 'false',
170
+ },
165
171
  });
166
172
 
173
+ const responseBody: EntryPointListResponse = {
174
+ ...response.body,
175
+ data: (response.body?.data ?? []).map(mapDialNumberEntryPoint),
176
+ };
177
+
167
178
  const duration = Date.now() - startTime;
168
179
 
169
- const recordCount = response.body?.data?.length || 0;
170
- const totalRecords = response.body?.meta?.totalRecords;
180
+ const recordCount = responseBody.data.length;
181
+ const totalRecords = responseBody.meta?.totalRecords;
171
182
 
172
183
  LoggerProxy.log(`Successfully retrieved ${recordCount} entry points`, {
173
184
  module: 'EntryPoint',
@@ -199,19 +210,7 @@ export class EntryPoint {
199
210
  );
200
211
  }
201
212
 
202
- // Cache the page data for simple pagination (no search/filter/attributes/sort)
203
- if (this.pageCache.canUseCache({search, filter, attributes, sortBy}) && response.body?.data) {
204
- const cacheKey = this.pageCache.buildCacheKey(orgId, page, pageSize);
205
- this.pageCache.cachePage(cacheKey, response.body.data, response.body.meta);
206
-
207
- LoggerProxy.log('Cached entry points data for future requests', {
208
- module: 'EntryPoint',
209
- method: 'getEntryPoints',
210
- data: {cacheKey, recordCount},
211
- });
212
- }
213
-
214
- return response.body;
213
+ return responseBody;
215
214
  } catch (error) {
216
215
  const errorData = {
217
216
  orgId,
@@ -70,14 +70,16 @@ export class Queue {
70
70
  }
71
71
 
72
72
  /**
73
- * Fetches contact service queues for the organization
74
- * @param {ContactServiceQueueSearchParams} [params] - Search and pagination parameters
73
+ * Fetches contact service queues for the organization. By default, returns active inbound
74
+ * telephony queues in backend name-ascending order using the agent's desktop profile views.
75
+ * Existing filter, sort, and desktop-profile parameters override those defaults.
76
+ * @param {ContactServiceQueueSearchParams} [params] - Search, pagination, and override parameters
75
77
  * @returns {Promise<ContactServiceQueuesResponse>} Promise resolving to contact service queues
76
78
  * @throws {Error} If the API call fails
77
79
  * @public
78
80
  * @example
79
81
  * ```typescript
80
- * // Get all queues with default pagination
82
+ * // Get queues using the default list policy and pagination
81
83
  * const response = await queueAPI.getQueues();
82
84
  *
83
85
  * // Get queues with specific pagination
@@ -101,17 +103,29 @@ export class Queue {
101
103
  page = PAGINATION_DEFAULTS.PAGE,
102
104
  pageSize = PAGINATION_DEFAULTS.PAGE_SIZE,
103
105
  search,
104
- filter,
106
+ filter = 'queueType==INBOUND;channelType==TELEPHONY;active==true',
105
107
  attributes,
106
- sortBy,
107
- sortOrder,
108
- desktopProfileFilter,
108
+ sortBy = 'name',
109
+ sortOrder = 'asc',
110
+ desktopProfileFilter = true,
109
111
  provisioningView,
110
112
  singleObjectResponse,
111
113
  } = params;
114
+ const agentView = desktopProfileFilter;
115
+ const firstLevelView = desktopProfileFilter;
112
116
 
113
117
  const orgId = this.webex.credentials.getOrgId();
114
- const isSearchRequest = !!(search || filter || attributes || sortBy);
118
+ const effectiveSortBy = sortBy ?? (sortOrder ? 'name' : undefined);
119
+ const isSearchRequest = !!(search || filter || attributes || effectiveSortBy);
120
+ const canUseCache = this.pageCache.canUseCache({
121
+ search,
122
+ filter,
123
+ attributes,
124
+ sortBy: effectiveSortBy,
125
+ desktopProfileFilter,
126
+ provisioningView,
127
+ singleObjectResponse,
128
+ });
115
129
 
116
130
  LoggerProxy.info('Fetching contact service queues', {
117
131
  module: 'Queue',
@@ -125,7 +139,7 @@ export class Queue {
125
139
  });
126
140
 
127
141
  // Check if we can use cache for simple pagination (no search/filter/attributes/sort)
128
- if (this.pageCache.canUseCache({search, filter, attributes, sortBy})) {
142
+ if (canUseCache) {
129
143
  const cacheKey = this.pageCache.buildCacheKey(orgId, page, pageSize);
130
144
  const cachedPage = this.pageCache.getCachedPage(cacheKey);
131
145
 
@@ -170,14 +184,17 @@ export class Queue {
170
184
  if (filter) queryParams.append('filter', filter);
171
185
  if (attributes) queryParams.append('attributes', attributes);
172
186
  if (search) queryParams.append('search', search);
173
- if (sortBy) queryParams.append('sortBy', sortBy);
174
- if (sortOrder) queryParams.append('sortOrder', sortOrder);
187
+ if (effectiveSortBy)
188
+ queryParams.append('sort', `${effectiveSortBy},${(sortOrder ?? 'asc').toUpperCase()}`);
175
189
  if (desktopProfileFilter !== undefined)
176
190
  queryParams.append('desktopProfileFilter', desktopProfileFilter.toString());
177
191
  if (provisioningView !== undefined)
178
192
  queryParams.append('provisioningView', provisioningView.toString());
179
193
  if (singleObjectResponse !== undefined)
180
194
  queryParams.append('singleObjectResponse', singleObjectResponse.toString());
195
+ if (agentView !== undefined) queryParams.append('agentView', agentView.toString());
196
+ if (firstLevelView !== undefined)
197
+ queryParams.append('firstLevelView', firstLevelView.toString());
181
198
 
182
199
  const resource = endPointMap.queueList(orgId, queryParams.toString());
183
200
 
@@ -232,7 +249,7 @@ export class Queue {
232
249
  }
233
250
 
234
251
  // Cache the page data for simple pagination (no search/filter/attributes/sort)
235
- if (this.pageCache.canUseCache({search, filter, attributes, sortBy}) && response.body?.data) {
252
+ if (canUseCache && response.body?.data) {
236
253
  const cacheKey = this.pageCache.buildCacheKey(orgId, page, pageSize);
237
254
  this.pageCache.cachePage(cacheKey, response.body.data, response.body.meta);
238
255