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

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 +5 -1
  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 +176 -20
  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 +1 -1
  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 +254 -15
  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 +117 -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 +46 -22
  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 +653 -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
@@ -0,0 +1,212 @@
1
+ import LoggerProxy from '../logger-proxy';
2
+ import {METHODS} from '../constants';
3
+ import {WebexSDK} from '../types';
4
+
5
+ const WEBEX_CROSS_CLIENT_FILE = 'WebexCrossClientService';
6
+ const DEFAULT_CROSS_CLIENT_STATE_TTL = 900;
7
+ const EXPIRATION_OFFSET_MS = 60_000;
8
+ /** Must match Agent Desktop usersub publish — Webex App reads `wxcc` cross-client state. */
9
+ const DEFAULT_APP_NAME = 'wxcc';
10
+ const REFRESH_RETRY_DELAY_MS = 30_000;
11
+ const MAX_REFRESH_RETRIES = 3;
12
+
13
+ type RefreshTimer = ReturnType<typeof setTimeout>;
14
+
15
+ /**
16
+ * Publishes usersub cross-client state so Webex App suppresses native calling toasts (WXCC-6026).
17
+ */
18
+ export default class WebexCrossClientService {
19
+ private webex: WebexSDK;
20
+ private refreshTimer?: RefreshTimer;
21
+ private refreshRetryTimer?: RefreshTimer;
22
+ private answerCallsState = false;
23
+ private appName = DEFAULT_APP_NAME;
24
+ /** Incremented on teardown/disable to ignore stale refresh timer callbacks. */
25
+ private refreshGeneration = 0;
26
+
27
+ constructor(webex: WebexSDK) {
28
+ this.webex = webex;
29
+ }
30
+
31
+ private extractDeviceIdFromUrl(url: string): string {
32
+ const match = url.match(/\/devices\/([^/?]+)/);
33
+
34
+ return match ? match[1] : '';
35
+ }
36
+
37
+ private getDeviceUrl(): string | undefined {
38
+ return this.webex.internal.device?.url;
39
+ }
40
+
41
+ private clearRefreshTimer(): void {
42
+ if (this.refreshTimer) {
43
+ clearTimeout(this.refreshTimer);
44
+ this.refreshTimer = undefined;
45
+ }
46
+
47
+ if (this.refreshRetryTimer) {
48
+ clearTimeout(this.refreshRetryTimer);
49
+ this.refreshRetryTimer = undefined;
50
+ }
51
+ }
52
+
53
+ private scheduleRefreshRetry(
54
+ userId: string,
55
+ ttl: number,
56
+ scheduledGeneration: number,
57
+ retryAttempt: number
58
+ ): void {
59
+ if (
60
+ retryAttempt >= MAX_REFRESH_RETRIES ||
61
+ scheduledGeneration !== this.refreshGeneration ||
62
+ !this.answerCallsState
63
+ ) {
64
+ return;
65
+ }
66
+
67
+ this.refreshRetryTimer = setTimeout(async () => {
68
+ if (scheduledGeneration !== this.refreshGeneration || !this.answerCallsState) {
69
+ return;
70
+ }
71
+
72
+ try {
73
+ await this.setManageWebexCallingInWxcc(true, {userId, ttl, appName: this.appName});
74
+ } catch (error) {
75
+ LoggerProxy.error(`WebexCrossClientService refresh retry failed: ${error}`, {
76
+ module: WEBEX_CROSS_CLIENT_FILE,
77
+ method: METHODS.SET_MANAGE_WEBEX_CALLING_IN_WXCC,
78
+ });
79
+ this.scheduleRefreshRetry(userId, ttl, scheduledGeneration, retryAttempt + 1);
80
+ }
81
+ }, REFRESH_RETRY_DELAY_MS);
82
+ }
83
+
84
+ private startRefreshTimer(userId: string, ttl: number): void {
85
+ this.clearRefreshTimer();
86
+
87
+ if (!this.answerCallsState) {
88
+ return;
89
+ }
90
+
91
+ const refreshTime = ttl * 1000 - EXPIRATION_OFFSET_MS;
92
+ const scheduledGeneration = this.refreshGeneration;
93
+ this.refreshTimer = setTimeout(async () => {
94
+ if (scheduledGeneration !== this.refreshGeneration || !this.answerCallsState) {
95
+ return;
96
+ }
97
+
98
+ try {
99
+ await this.setManageWebexCallingInWxcc(true, {userId, ttl, appName: this.appName});
100
+ } catch (error) {
101
+ LoggerProxy.error(`WebexCrossClientService refresh failed: ${error}`, {
102
+ module: WEBEX_CROSS_CLIENT_FILE,
103
+ method: METHODS.SET_MANAGE_WEBEX_CALLING_IN_WXCC,
104
+ });
105
+ this.scheduleRefreshRetry(userId, ttl, scheduledGeneration, 0);
106
+ }
107
+ }, refreshTime);
108
+ }
109
+
110
+ private publishCrossClientState(
111
+ users: string[],
112
+ ttl: number,
113
+ composition: Record<string, unknown>,
114
+ logContext: string
115
+ ): Promise<unknown> {
116
+ const body = {
117
+ users,
118
+ compositions: [
119
+ {
120
+ type: 'cross-client-state',
121
+ ttl,
122
+ composition,
123
+ },
124
+ ],
125
+ };
126
+
127
+ return this.webex
128
+ .request({
129
+ method: 'POST',
130
+ service: 'usersub',
131
+ resource: 'publish',
132
+ body,
133
+ })
134
+ .then((response: {body?: unknown}) => response?.body)
135
+ .catch((error: unknown) => {
136
+ LoggerProxy.error(`WebexCrossClientService.${logContext} failed: ${error}`, {
137
+ module: WEBEX_CROSS_CLIENT_FILE,
138
+ method: METHODS.SET_MANAGE_WEBEX_CALLING_IN_WXCC,
139
+ });
140
+
141
+ return Promise.reject(error);
142
+ });
143
+ }
144
+
145
+ public async setManageWebexCallingInWxcc(
146
+ enable: boolean,
147
+ options?: {userId?: string; ttl?: number; appName?: string}
148
+ ): Promise<void> {
149
+ const operationGeneration = this.refreshGeneration;
150
+ const userId = options?.userId ?? this.webex.internal.device?.userId;
151
+ const ttl = options?.ttl ?? DEFAULT_CROSS_CLIENT_STATE_TTL;
152
+ const appName = options?.appName ?? DEFAULT_APP_NAME;
153
+ this.appName = appName;
154
+
155
+ if (!userId) {
156
+ throw new Error('User ID is unavailable for cross-client publish');
157
+ }
158
+
159
+ const deviceUrl = this.getDeviceUrl();
160
+ if (!deviceUrl) {
161
+ throw new Error('Device URL is unavailable for cross-client publish');
162
+ }
163
+
164
+ const composition = {
165
+ devices: [
166
+ {
167
+ deviceId: this.extractDeviceIdFromUrl(deviceUrl),
168
+ appName,
169
+ state: {
170
+ 'answer-calls-on-wxcc': enable,
171
+ },
172
+ },
173
+ ],
174
+ };
175
+
176
+ await this.publishCrossClientState([userId], ttl, composition, 'setManageWebexCallingInWxcc');
177
+
178
+ if (operationGeneration !== this.refreshGeneration) {
179
+ return;
180
+ }
181
+
182
+ this.answerCallsState = enable;
183
+
184
+ if (enable) {
185
+ this.startRefreshTimer(userId, ttl);
186
+ } else {
187
+ this.refreshGeneration += 1;
188
+ this.clearRefreshTimer();
189
+ }
190
+
191
+ LoggerProxy.info(`Cross-client answer-calls-on-wxcc set to ${enable}`, {
192
+ module: WEBEX_CROSS_CLIENT_FILE,
193
+ method: METHODS.SET_MANAGE_WEBEX_CALLING_IN_WXCC,
194
+ data: {
195
+ appName,
196
+ deviceId: this.extractDeviceIdFromUrl(deviceUrl),
197
+ ttl,
198
+ },
199
+ });
200
+ }
201
+
202
+ public teardown(): void {
203
+ this.refreshGeneration += 1;
204
+ this.clearRefreshTimer();
205
+ this.answerCallsState = false;
206
+ }
207
+
208
+ /** Whether usersub `answer-calls-on-wxcc: true` was successfully published this session. */
209
+ public isAnswerCallsStateActive(): boolean {
210
+ return this.answerCallsState;
211
+ }
212
+ }
@@ -0,0 +1,115 @@
1
+ import LoggerProxy from '../logger-proxy';
2
+ import {METHODS} from '../constants';
3
+ import {WebexSDK} from '../types';
4
+
5
+ const WXAPP_TELEPHONY_MERCURY_SYNC_FILE = 'WxAppTelephonyMercurySync';
6
+
7
+ export const TELEPHONY_CALL_MUTED = 'event:telephony_calls.muted';
8
+ export const TELEPHONY_CALL_UNMUTED = 'event:telephony_calls.unmuted';
9
+
10
+ export type UpdatedCallMuteStateEvent = {
11
+ data: {
12
+ actorId: string;
13
+ callId: string;
14
+ muted: boolean;
15
+ };
16
+ };
17
+
18
+ type MuteChangeHandler = (callId: string, muted: boolean) => void;
19
+
20
+ /**
21
+ * Subscribes to Mercury telephony mute/unmute events for wxApp thick-client sync (AD parity).
22
+ */
23
+ export default class WxAppTelephonyMercurySync {
24
+ private webex: WebexSDK;
25
+ private subscribedAgentId?: string;
26
+ private muteChangeHandler?: MuteChangeHandler;
27
+ private boundMuteHandler?: (event: UpdatedCallMuteStateEvent) => void;
28
+ private boundUnmuteHandler?: (event: UpdatedCallMuteStateEvent) => void;
29
+
30
+ constructor(webex: WebexSDK) {
31
+ this.webex = webex;
32
+ }
33
+
34
+ private decodeBase64(value: string): string {
35
+ try {
36
+ return atob(value);
37
+ } catch {
38
+ return value;
39
+ }
40
+ }
41
+
42
+ private handleMuteStateEvent = (agentId: string, event: UpdatedCallMuteStateEvent): void => {
43
+ const {actorId, callId, muted} = event?.data ?? {};
44
+ if (!actorId || !callId || muted === undefined) {
45
+ return;
46
+ }
47
+
48
+ const decodedActorId = this.decodeBase64(actorId);
49
+ const decodedCallId = this.decodeBase64(callId);
50
+
51
+ if (!decodedActorId.endsWith(agentId)) {
52
+ return;
53
+ }
54
+
55
+ LoggerProxy.info('WxApp telephony mute state sync received', {
56
+ module: WXAPP_TELEPHONY_MERCURY_SYNC_FILE,
57
+ method: METHODS.SYNC_WXAPP_MUTE_FROM_MERCURY,
58
+ data: {muted},
59
+ });
60
+
61
+ this.muteChangeHandler?.(decodedCallId, muted);
62
+ };
63
+
64
+ public subscribe(agentId: string, onMuteChange: MuteChangeHandler): void {
65
+ this.unsubscribe();
66
+
67
+ const mercury = this.webex.internal.mercury;
68
+ if (!mercury) {
69
+ LoggerProxy.error('Mercury is unavailable for wxApp mute sync', {
70
+ module: WXAPP_TELEPHONY_MERCURY_SYNC_FILE,
71
+ method: METHODS.SYNC_WXAPP_MUTE_FROM_MERCURY,
72
+ });
73
+
74
+ return;
75
+ }
76
+
77
+ this.subscribedAgentId = agentId;
78
+ this.muteChangeHandler = onMuteChange;
79
+
80
+ this.boundMuteHandler = (event: UpdatedCallMuteStateEvent) => {
81
+ this.handleMuteStateEvent(agentId, event);
82
+ };
83
+ this.boundUnmuteHandler = (event: UpdatedCallMuteStateEvent) => {
84
+ this.handleMuteStateEvent(agentId, event);
85
+ };
86
+
87
+ mercury.on(TELEPHONY_CALL_MUTED, this.boundMuteHandler);
88
+ mercury.on(TELEPHONY_CALL_UNMUTED, this.boundUnmuteHandler);
89
+
90
+ LoggerProxy.info('Subscribed to wxApp telephony Mercury mute sync', {
91
+ module: WXAPP_TELEPHONY_MERCURY_SYNC_FILE,
92
+ method: METHODS.SYNC_WXAPP_MUTE_FROM_MERCURY,
93
+ });
94
+ }
95
+
96
+ public unsubscribe(): void {
97
+ const mercury = this.webex.internal.mercury;
98
+
99
+ if (mercury && this.boundMuteHandler) {
100
+ mercury.off(TELEPHONY_CALL_MUTED, this.boundMuteHandler);
101
+ }
102
+ if (mercury && this.boundUnmuteHandler) {
103
+ mercury.off(TELEPHONY_CALL_UNMUTED, this.boundUnmuteHandler);
104
+ }
105
+
106
+ this.subscribedAgentId = undefined;
107
+ this.muteChangeHandler = undefined;
108
+ this.boundMuteHandler = undefined;
109
+ this.boundUnmuteHandler = undefined;
110
+ }
111
+
112
+ public isSubscribed(): boolean {
113
+ return Boolean(this.subscribedAgentId && this.muteChangeHandler);
114
+ }
115
+ }
@@ -109,12 +109,12 @@ src/services/
109
109
  │ └── ARCHITECTURE.md
110
110
  │
111
111
  ├── AddressBook.ts # Address book entries — getEntries() with pagination/cache
112
- ├── EntryPoint.ts # Entry points — getEntryPoints() with pagination/cache
112
+ ├── EntryPoint.ts # Profile-scoped entry-point dial-number mappings (uncached)
113
113
  ├── Queue.ts # Queues — getQueues() with pagination/cache
114
114
  └── WebCallingService.ts # WebRTC calling — register/deregister line, answer/mute/decline
115
115
  ```
116
116
 
117
- Note: The `src/utils/` folder (sibling to `src/services/`) contains shared utilities like [`PageCache.ts`](../../utils/PageCache.ts) which provides generic pagination caching with `BaseSearchParams`, `PaginatedResponse`, and `PaginationMeta` types used by all data services.
117
+ Note: The `src/utils/` folder (sibling to `src/services/`) contains [`PageCache.ts`](../../utils/PageCache.ts), which provides pagination contracts/defaults shared by data-service types and optional page caching used by AddressBook and Queue. EntryPoint deliberately bypasses PageCache because every request is profile-scoped.
118
118
 
119
119
  ### Shared Constants (`src/services/constants.ts`)
120
120
 
@@ -137,8 +137,8 @@ Use [`constants.ts`](../constants.ts) as the canonical source for service-level
137
137
  | **Task Management** | [`task/`](../task/TaskManager.ts) | Task lifecycle (accept, hold, transfer, conference, wrapup), Task state machine, contact operations, outbound dialing |
138
138
  | **Configuration** | [`config/`](../config/index.ts) | Agent profile aggregation from 8+ API endpoints, org settings, teams, aux codes, dial plans |
139
139
  | **Core Infrastructure** | [`core/`](../core/WebexRequest.ts) | HTTP requests (`WebexRequest`), WebSocket management (`WebSocketManager`), connection lifecycle (`ConnectionService`), AQM request/response correlation (`AqmReqs`), error handling (`Utils`, `Err`) |
140
- | **Data Services** | [`AddressBook.ts`](../AddressBook.ts), [`Queue.ts`](../Queue.ts), [`EntryPoint.ts`](../EntryPoint.ts) | Standalone REST-based data services with pagination and caching for address books, queues, and entry points |
141
- | **Utilities** | [`src/utils/PageCache.ts`](../../utils/PageCache.ts) | Shared `PageCache<T>` generic class for pagination caching, plus `BaseSearchParams`, `PaginatedResponse`, and `PaginationMeta` types used by all data services |
140
+ | **Data Services** | [`AddressBook.ts`](../AddressBook.ts), [`Queue.ts`](../Queue.ts), [`EntryPoint.ts`](../EntryPoint.ts) | Standalone REST-based paginated services; AddressBook and Queue optionally cache eligible pages, while profile-scoped EntryPoint requests are always fetched |
141
+ | **Utilities** | [`src/utils/PageCache.ts`](../../utils/PageCache.ts) | Shared pagination contracts/defaults plus optional `PageCache<T>` reuse for AddressBook and Queue |
142
142
  | **WebRTC Calling** | [`WebCallingService.ts`](../WebCallingService.ts) | Browser-based voice calling via `@webex/calling`, line registration, call answer/mute/decline |
143
143
 
144
144
  ---
@@ -156,7 +156,7 @@ Each service folder contains its own `ai-docs/` with detailed documentation. **A
156
156
 
157
157
  > **Note**: The task state machine (`task/state-machine/`) is part of the Task service, not a separate service. Its dedicated docs live at [`task/state-machine/ai-docs/AGENTS.md`](../task/state-machine/ai-docs/AGENTS.md) and [`ARCHITECTURE.md`](../task/state-machine/ai-docs/ARCHITECTURE.md). Load these when working on state transitions, guards, or actions.
158
158
 
159
- **Data services** (AddressBook, Queue, EntryPoint) do not have dedicated ai-docs. Read their source files directly — they follow shared REST/pagination/caching patterns documented in [`ai-docs/patterns/typescript-patterns.md`](../../../ai-docs/patterns/typescript-patterns.md).
159
+ **Data services** (AddressBook, Queue, EntryPoint) do not have dedicated ai-docs. Read their source files directly. They share direct REST, pagination, logging, metrics, and error propagation, but cache policy is service-owned: AddressBook/Queue use PageCache when eligible and EntryPoint always fetches its profile-scoped mapping.
160
160
 
161
161
  **WebCallingService** also has no dedicated ai-docs, but it follows a different pattern: EventEmitter-based call lifecycle orchestration around `@webex/calling` (`createClient`, line registration/deregistration, `ICall` events), `callTaskMap` tracking, and async registration flows with timeout handling. Read [`WebCallingService.ts`](../WebCallingService.ts) directly when changing browser calling behavior.
162
162
 
@@ -271,16 +271,16 @@ const services = Services.getInstance({
271
271
 
272
272
  ## Data Services Pattern (AddressBook, Queue, EntryPoint)
273
273
 
274
- These three services share an identical pattern. Use any one as a reference when creating similar services:
274
+ These services share the direct REST and pagination pattern, with service-specific request mapping and cache policy:
275
275
 
276
276
  | Aspect | Pattern |
277
277
  |---|---|
278
- | **Class structure** | Standalone class with `WebexRequest`, `WebexSDK`, `MetricsManager`, `PageCache` |
278
+ | **Class structure** | Standalone class with `WebexRequest`, `WebexSDK`, and `MetricsManager`; cache-enabled services additionally own `PageCache` |
279
279
  | **Constructor** | `constructor(webex: WebexSDK)` — gets singletons via `.getInstance()` |
280
280
  | **HTTP calls** | `this.webexRequest.request({service: WCC_API_GATEWAY, resource, method: HTTP_METHODS.GET})` |
281
281
  | **Endpoints** | Uses `endPointMap` functions from `config/constants.ts` to build URL paths |
282
- | **Pagination** | Query params with `page`, `pageSize`; uses `PageCache` for caching |
283
- | **Caching** | `PageCache<T>` — caches pages for simple pagination, bypasses cache for search/filter |
282
+ | **Pagination** | Query params with `page` and `pageSize` |
283
+ | **Caching** | AddressBook/Queue may use `PageCache<T>` for eligible pages; EntryPoint always fetches because its request is desktop-profile scoped |
284
284
  | **Metrics** | `timeEvent` on API call start, `trackEvent` on success/failure |
285
285
  | **Logging** | `LoggerProxy` with `{module: 'ClassName', method: 'methodName'}` context |
286
286
  | **Error handling** | try/catch with `LoggerProxy.error` + `metricsManager.trackEvent` for failures, then re-throw so callers receive the error |
@@ -288,7 +288,7 @@ These three services share an identical pattern. Use any one as a reference when
288
288
  Reference files:
289
289
  - [`AddressBook.ts`](../AddressBook.ts) — includes `addressBookId` parameter
290
290
  - [`Queue.ts`](../Queue.ts) — includes additional query params (sortBy, sortOrder, etc.)
291
- - [`EntryPoint.ts`](../EntryPoint.ts) — simplest example
291
+ - [`EntryPoint.ts`](../EntryPoint.ts) — profile-scoped dial-number mapping without PageCache
292
292
 
293
293
  ---
294
294
 
@@ -54,6 +54,7 @@ The `src/services/` directory is the service layer of the `@webex/contact-center
54
54
  | **User Preferences** | [`UserPreference.ts`](../UserPreference.ts) | Direct REST CRUD service exposed as `cc.userPreference`; it resolves the default user from agent configuration and does not use PageCache |
55
55
  | **Utilities** | [`src/utils/PageCache.ts`](../../utils/PageCache.ts) | Shared `PageCache<T>` generic class for pagination caching, plus `BaseSearchParams`, `PaginatedResponse`, and `PaginationMeta` types used by all data services |
56
56
  | **WebRTC Calling** | [`WebCallingService.ts`](../WebCallingService.ts) | Browser-based voice calling via `@webex/calling`, line registration, call answer/mute/decline |
57
+ | **WxApp Better Together** | [`WebexCrossClientService.ts`](../WebexCrossClientService.ts), [`AnswerCallOnWebexService.ts`](../AnswerCallOnWebexService.ts), [`WxAppTelephonyMercurySync.ts`](../WxAppTelephonyMercurySync.ts) | usersub cross-client publish (`answer-calls-on-wxcc`), Hydra telephony REST (answer/reject/mute/DTMF/GET call), Mercury mute sync — orchestrated from `cc.ts` when `enableWxBetterTogether` is active |
57
58
 
58
59
  Each service folder contains its own `ai-docs/` with detailed documentation. **Always load the relevant service docs before making changes.**
59
60
 
@@ -227,6 +228,7 @@ Use [`constants.ts`](../constants.ts) as the canonical source for service-level
227
228
  | Contract ID | Type | Surface | Purpose | Compatibility / deprecation | Schema / detail link | Root index |
228
229
  |---|---|---|---|---|---|---|
229
230
  | `services.surface` | SDK / event / internal API | Internal `Services.getInstance()` composition root plus data-service and calling collaborators consumed by `ContactCenter`. | Stable module consumption boundary. | Additive changes by default; breaking package exports require a major-version transition. | `src/services/index.ts` | `../../../ai-docs/CONTRACTS.md` |
231
+ | `services.destination-lists` | SDK data API | Existing `Queue.getQueues(ContactServiceQueueSearchParams)` and `EntryPoint.getEntryPoints(EntryPointSearchParams)` methods with full-record paginated responses. | Apply consult/transfer telephony eligibility, profile views, and backend name ordering as overridable defaults. | Behavioral default correction on existing methods; no specialized method, projected response, or replacement signature. | `src/services/Queue.ts`, `src/services/EntryPoint.ts`, `src/types.ts` | `../../../ai-docs/CONTRACTS.md` |
230
232
  | `services.user-preference` | SDK data API | `UserPreference` and `cc.userPreference`. | Direct REST CRUD for user preferences; separate from PageCache-based pagination services. | Additive semver-public API; removals or signature changes are breaking. | `src/services/UserPreference.ts`, `src/services/config/types.ts` | `../../../ai-docs/CONTRACTS.md` |
231
233
 
232
234
  Compatibility notes:
@@ -261,6 +263,7 @@ ContactCenter READY callback
261
263
  | SERVICES-R-004 | Pass ApiAIAssistant, contact routing, WebCallingService, primary WebSocket, and RTD WebSocket into TaskManager. | Voice, task, transcript, and suggestion behavior depend on the complete collaborator set. | `src/cc.ts` | `test/unit/spec/services/task/TaskManager.ts` | None; source and test evidence rechecked during the 2026-07-09 remediation; independent document revalidation pending. | PRESENT |
262
264
  | SERVICES-R-005 | Inherit authenticated request identity from the host Webex SDK through Core/WebexRequest; Services must not store, parse, or refresh credentials. | One host-owned authentication boundary avoids duplicate token handling and credential leakage across composed services. | `src/services/index.ts`, `src/services/core/WebexRequest.ts` | `test/unit/spec/services/core/WebexRequest.ts` | None; authentication ownership is explicit. | PRESENT |
263
265
  | SERVICES-R-006 | Treat Services composition as unconditionally created by the ContactCenter READY callback; Services owns no rollout or feature-flag decision. | Capability flags belong to the consuming config/task/calling collaborators, so the composition root must not silently gate construction. | `src/services/index.ts`, `src/cc.ts` | `test/unit/spec/cc.ts` | None; rollout applicability is explicitly N/A for Services. | PRESENT |
266
+ | SERVICES-R-007 | Existing Queue and EntryPoint list methods must apply inbound, active, telephony, profile/agent-view, and `name,ASC` defaults while retaining their established parameter and full-record response types. Caller-supplied existing filter, sort, or profile inputs override defaults. Queue must also treat `sortOrder` without `sortBy` as a name sort and bypass the simple-page cache. | Defaults on the established methods let thin consumers request lists without a parallel API, while existing parameters preserve specialized behavior for other consumers and full-record types remain truthful because no field projection is requested. | `src/services/Queue.ts`, `src/services/EntryPoint.ts`, `src/types.ts` | `test/unit/spec/services/Queue.ts`, `test/unit/spec/services/EntryPoint.ts`, `test/unit/spec/cc.ts` | The backend honors the view flags and combined CMS sort value. | PRESENT |
264
267
 
265
268
  ## Design Overview
266
269
  `Services` is a singleton composition root for transport-facing capabilities only. It constructs two `WebSocketManager` instances (primary Contact Center and RTD), creates `AqmReqs` on the primary manager, then creates config, agent, contact, dialer, and ConnectionService collaborators.
@@ -419,7 +422,8 @@ These three services share an identical pattern. Use any one as a reference when
419
422
  | **HTTP calls** | `this.webexRequest.request({service: WCC_API_GATEWAY, resource, method: HTTP_METHODS.GET})` |
420
423
  | **Endpoints** | Uses `endPointMap` functions from `config/constants.ts` to build URL paths |
421
424
  | **Pagination** | Query params with `page`, `pageSize`; uses `PageCache` for caching |
422
- | **Caching** | `PageCache<T>` — caches pages for simple pagination, bypasses cache for search/filter |
425
+ | **Caching** | `PageCache<T>` — caches pages for simple pagination and bypasses cache for result/shape variants, including an effective Queue sort |
426
+ | **Consult/transfer policy** | Existing Queue and EntryPoint methods apply telephony eligibility, profile views, and backend name ordering by default, accept overrides through their existing query parameters, and return full-record responses unchanged |
423
427
  | **Metrics** | `timeEvent` on API call start, `trackEvent` on success/failure |
424
428
  | **Logging** | `LoggerProxy` with `{module: 'ClassName', method: 'methodName'}` context |
425
429
  | **Error handling** | try/catch with `LoggerProxy.error` + `metricsManager.trackEvent` for failures, then re-throw so callers receive the error |
@@ -476,6 +480,7 @@ Unit tests mirror module paths under `test/unit/spec/services`. Preserve positiv
476
480
  | `SERVICES-R-004` | `test/unit/spec/services/task/TaskManager.ts`, `test/unit/spec/cc.ts` | None. |
477
481
  | `SERVICES-R-005` | `test/unit/spec/services/core/WebexRequest.ts` | None. |
478
482
  | `SERVICES-R-006` | `test/unit/spec/cc.ts` | None. |
483
+ | `SERVICES-R-007` | `test/unit/spec/services/Queue.ts`, `test/unit/spec/services/EntryPoint.ts`, `test/unit/spec/cc.ts` | None. |
479
484
 
480
485
  ## Traceability
481
486
  - Repo architecture: `../../../ai-docs/ARCHITECTURE.md` · Registry: `../../../ai-docs/SPEC_INDEX.md`
@@ -238,6 +238,9 @@ function parseAgentConfigs(profileData: {
238
238
  callVariablesSuppressed: tenantData.callVariablesSuppressed,
239
239
  agentDbId: userData.dbId,
240
240
  allowConsultToQueue: agentProfileData.consultToQueue,
241
+ accessQueue: agentProfileData.accessQueue,
242
+ accessEntryPoint: agentProfileData.accessEntryPoint,
243
+ accessBuddyTeam: agentProfileData.accessBuddyTeam,
241
244
  agentPersonalStatsEnabled: agentProfileData.viewableStatistics
242
245
  ? agentProfileData.viewableStatistics.agentStats
243
246
  : false,
@@ -156,7 +156,7 @@ Additionally, the following endpoints are defined in `constants.ts` `endPointMap
156
156
  | Endpoint | Used By | Description |
157
157
  |----------|---------|-------------|
158
158
  | `queueList` | [`Queue.ts`](../../Queue.ts) | Fetch contact service queues |
159
- | `entryPointList` | [`EntryPoint.ts`](../../EntryPoint.ts) | Fetch entry points |
159
+ | `entryPointDialNumberList` | [`EntryPoint.ts`](../../EntryPoint.ts) | Fetch profile-scoped entry-point dial-number mappings |
160
160
  | `addressBookEntries` | [`AddressBook.ts`](../../AddressBook.ts) | Fetch address book entries |
161
161
 
162
162
  ---
@@ -132,8 +132,8 @@ export const endPointMap = {
132
132
  queueList: (orgId: string, queryParams: string) =>
133
133
  `/organization/${orgId}/v2/contact-service-queue?${queryParams}`,
134
134
 
135
- entryPointList: (orgId: string, queryParams: string) =>
136
- `/organization/${orgId}/v2/entry-point?${queryParams}`,
135
+ entryPointDialNumberList: (orgId: string, queryParams: string) =>
136
+ `/organization/${orgId}/v3/dial-number?${queryParams}`,
137
137
 
138
138
  addressBookEntries: (orgId: string, addressBookId: string, queryParams: string) =>
139
139
  `/organization/${orgId}/v2/address-book/${addressBookId}/entry?${queryParams}`,
@@ -158,6 +158,12 @@ The AgentProfile is defined as the [`Profile`](../types.ts) type. This is not an
158
158
  | `dialPlan` | [`DialPlan`](../types.ts) | Number transformation rules |
159
159
  | `isOutboundEnabledForAgent` | boolean | Outbound calling allowed |
160
160
  | `outDialEp` | string | Outbound entry point ID |
161
+ | `allowConsultToQueue` | boolean | Whether consult-to-queue is enabled on the Desktop Profile |
162
+ | `accessQueue` | `'ALL' \| 'SPECIFIC' \| 'NONE'` | Collaboration tab queue access scope from Desktop Profile |
163
+ | `accessEntryPoint` | `'ALL' \| 'SPECIFIC' \| 'NONE'` | Collaboration tab entry-point access scope from Desktop Profile |
164
+ | `accessBuddyTeam` | `'ALL' \| 'SPECIFIC' \| 'NONE'` | Collaboration tab buddy-team access scope from Desktop Profile |
165
+
166
+ `parseAgentConfigs()` maps the three `access*` fields from `DesktopProfileResponse` onto the public `Profile` returned by `cc.register()`.
161
167
 
162
168
  The following diagram shows how `getAgentConfig` orchestrates multiple API calls and combines their results into the AgentProfile via `parseAgentConfigs()`:
163
169
 
@@ -1,6 +1,13 @@
1
1
  // making query params configurable for List Teams and List Aux Codes API
2
2
  export const DEFAULT_PAGE = 0;
3
3
 
4
+ /** Desktop Profile access levels for collaboration destination categories. */
5
+ export const COLLABORATION_ACCESS = {
6
+ ALL: 'ALL',
7
+ SPECIFIC: 'SPECIFIC',
8
+ NONE: 'NONE',
9
+ } as const;
10
+
4
11
  /**
5
12
  * Default page size for paginated API requests.
6
13
  * @type {number}
@@ -258,17 +265,14 @@ export const endPointMap = {
258
265
  queueList: (orgId: string, queryParams: string) =>
259
266
  `/organization/${orgId}/v2/contact-service-queue?${queryParams}`,
260
267
  /**
261
- * Gets the endpoint for entry points list with custom query parameters.
268
+ * Gets the desktop-profile-filtered dial-number mappings used by entry-point destination lists.
262
269
  * @param orgId - Organization ID.
263
270
  * @param queryParams - Query parameters string.
264
271
  * @returns The endpoint URL string.
265
- * @public
266
- * @example
267
- * const url = endPointMap.entryPointList('org123', 'page=0&pageSize=10');
268
272
  * @ignore
269
273
  */
270
- entryPointList: (orgId: string, queryParams: string) =>
271
- `/organization/${orgId}/v2/entry-point?${queryParams}`,
274
+ entryPointDialNumberList: (orgId: string, queryParams: string) =>
275
+ `/organization/${orgId}/v3/dial-number?${queryParams}`,
272
276
  /**
273
277
  * Gets the endpoint for address book entries with custom query parameters.
274
278
  * @param orgId - Organization ID.
@@ -1,4 +1,5 @@
1
1
  import * as Agent from '../agent/types';
2
+ import {COLLABORATION_ACCESS} from './constants';
2
3
 
3
4
  /**
4
5
  * Generic type for converting a const enum object into a union type of its values
@@ -6,6 +7,9 @@ import * as Agent from '../agent/types';
6
7
  */
7
8
  type Enum<T extends Record<string, unknown>> = T[keyof T];
8
9
 
10
+ /** Desktop Profile access level for a collaboration destination category. */
11
+ export type CollaborationAccess = Enum<typeof COLLABORATION_ACCESS>;
12
+
9
13
  /**
10
14
  * Events emitted on task objects
11
15
  * @enum {string}
@@ -57,6 +61,8 @@ export const CC_TASK_EVENTS = {
57
61
  PARTICIPANT_LEFT_CONFERENCE: 'ParticipantLeftConference',
58
62
  /** Event emitted when participant leaving conference fails */
59
63
  PARTICIPANT_LEFT_CONFERENCE_FAILED: 'ParticipantLeftConferenceFailed',
64
+ /** Event emitted when dropping another conference participant fails */
65
+ PARTICIPANT_DROP_CONFERENCE_FAILED: 'ParticipantDropConferenceFailed',
60
66
  /** Event emitted when consultation conference end fails */
61
67
  AGENT_CONSULT_CONFERENCE_END_FAILED: 'AgentConsultConferenceEndFailed',
62
68
  /** Event emitted when conference is successfully transferred */
@@ -404,9 +410,9 @@ export type DesktopProfileResponse = {
404
410
  allowAutoWrapUpExtension: boolean;
405
411
 
406
412
  /**
407
- * Access control for queues assigned to the agent (ALL or SPECIFIC).
413
+ * Access control for queues assigned to the agent (ALL, SPECIFIC, or NONE).
408
414
  */
409
- accessQueue: string;
415
+ accessQueue: CollaborationAccess;
410
416
 
411
417
  /**
412
418
  * Queue identifiers available to the agent when access is SPECIFIC.
@@ -416,7 +422,7 @@ export type DesktopProfileResponse = {
416
422
  /**
417
423
  * Access control for entry points assigned to the agent.
418
424
  */
419
- accessEntryPoint: string;
425
+ accessEntryPoint: CollaborationAccess;
420
426
 
421
427
  /**
422
428
  * Entry point identifiers available to the agent when access is SPECIFIC.
@@ -426,7 +432,7 @@ export type DesktopProfileResponse = {
426
432
  /**
427
433
  * Access control for buddy teams assigned to the agent.
428
434
  */
429
- accessBuddyTeam: string;
435
+ accessBuddyTeam: CollaborationAccess;
430
436
 
431
437
  /**
432
438
  * Buddy team identifiers available to the agent when access is SPECIFIC.
@@ -1169,6 +1175,12 @@ export type Profile = {
1169
1175
  agentAnalyzerId?: string;
1170
1176
  /** Whether consult to queue is allowed */
1171
1177
  allowConsultToQueue: boolean;
1178
+ /** Access control for queues on Desktop Profile Collaboration tab */
1179
+ accessQueue?: CollaborationAccess;
1180
+ /** Access control for entry points on Desktop Profile Collaboration tab */
1181
+ accessEntryPoint?: CollaborationAccess;
1182
+ /** Access control for buddy teams on Desktop Profile Collaboration tab */
1183
+ accessBuddyTeam?: CollaborationAccess;
1172
1184
  /** Additional campaign manager information */
1173
1185
  campaignManagerAdditionalInfo?: string;
1174
1186
  /** Whether personal statistics are enabled */
@@ -30,6 +30,7 @@ export type TaskErrorIds =
30
30
  | {'Service.aqm.err.trackingId': {trackingId: string}}
31
31
  | {'Service.aqm.task.consultAccept': Failure}
32
32
  | {'Service.aqm.task.consultConference': Failure}
33
+ | {'Service.aqm.task.dropConferenceParticipant': Failure}
33
34
  | {'Service.aqm.task.consultEnd': Failure}
34
35
  | {'Service.aqm.task.cancelCtq': Failure}
35
36
  | {'Service.aqm.task.hold': Failure}
@@ -34,14 +34,16 @@ class WebexRequest {
34
34
  resource: string;
35
35
  method: HTTP_METHODS;
36
36
  body?: RequestBody;
37
+ headers?: Record<string, string | null>;
37
38
  }): Promise<IHttpResponse> {
38
- const {service, resource, method, body} = options;
39
+ const {service, resource, method, body, headers} = options;
39
40
 
40
41
  return this.webex.request({
41
42
  service,
42
43
  resource,
43
44
  method,
44
45
  body,
46
+ ...(headers ? {headers} : {}),
45
47
  });
46
48
  }
47
49