@webex/contact-center 3.12.0-next.9 → 3.12.0-next.91

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 (229) hide show
  1. package/.sdd/manifest.json +876 -0
  2. package/AGENTS.md +94 -0
  3. package/ai-docs/ARCHITECTURE.md +168 -0
  4. package/ai-docs/CONTRACTS.md +46 -0
  5. package/ai-docs/GETTING_STARTED.md +168 -0
  6. package/ai-docs/GLOSSARY.md +43 -0
  7. package/ai-docs/README.md +138 -0
  8. package/ai-docs/REVIEW_CHECKLIST.md +41 -0
  9. package/ai-docs/RULES.md +444 -0
  10. package/ai-docs/SECURITY.md +52 -0
  11. package/ai-docs/SERVICE_STATE.md +48 -0
  12. package/ai-docs/SPEC_INDEX.md +65 -0
  13. package/ai-docs/adr/0001-spec-source-policy.md +55 -0
  14. package/ai-docs/adr/README.md +8 -0
  15. package/ai-docs/adr/_adr-template.md +31 -0
  16. package/ai-docs/contact-center-spec.md +341 -0
  17. package/ai-docs/features/generated-spec-conformance-fidelity-remediation/spec/feature-spec.md +117 -0
  18. package/ai-docs/features/residual-warning-coverage-completion/spec/feature-spec.md +203 -0
  19. package/ai-docs/features/validator-code-fidelity-drift-fix/spec/feature-spec.md +315 -0
  20. package/ai-docs/patterns/event-driven-patterns.md +485 -0
  21. package/ai-docs/patterns/testing-patterns.md +480 -0
  22. package/ai-docs/patterns/typescript-patterns.md +365 -0
  23. package/ai-docs/templates/README.md +102 -0
  24. package/ai-docs/templates/documentation/create-agents-md.md +240 -0
  25. package/ai-docs/templates/documentation/create-architecture-md.md +295 -0
  26. package/ai-docs/templates/existing-service/bug-fix.md +254 -0
  27. package/ai-docs/templates/existing-service/feature-enhancement.md +450 -0
  28. package/ai-docs/templates/new-method/00-master.md +80 -0
  29. package/ai-docs/templates/new-method/01-requirements.md +232 -0
  30. package/ai-docs/templates/new-method/02-implementation.md +295 -0
  31. package/ai-docs/templates/new-method/03-tests.md +201 -0
  32. package/ai-docs/templates/new-method/04-validation.md +141 -0
  33. package/ai-docs/templates/new-service/00-master.md +109 -0
  34. package/ai-docs/templates/new-service/01-pre-questions.md +159 -0
  35. package/ai-docs/templates/new-service/02-code-generation.md +346 -0
  36. package/ai-docs/templates/new-service/03-integration.md +178 -0
  37. package/ai-docs/templates/new-service/04-test-generation.md +205 -0
  38. package/ai-docs/templates/new-service/05-validation.md +145 -0
  39. package/dist/cc.js +265 -29
  40. package/dist/cc.js.map +1 -1
  41. package/dist/config.js +6 -0
  42. package/dist/config.js.map +1 -1
  43. package/dist/constants.js +17 -1
  44. package/dist/constants.js.map +1 -1
  45. package/dist/index.js +20 -5
  46. package/dist/index.js.map +1 -1
  47. package/dist/metrics/behavioral-events.js +101 -0
  48. package/dist/metrics/behavioral-events.js.map +1 -1
  49. package/dist/metrics/constants.js +25 -4
  50. package/dist/metrics/constants.js.map +1 -1
  51. package/dist/services/ApiAiAssistant.js +153 -8
  52. package/dist/services/ApiAiAssistant.js.map +1 -1
  53. package/dist/services/UserPreference.js +427 -0
  54. package/dist/services/UserPreference.js.map +1 -0
  55. package/dist/services/config/Util.js +3 -3
  56. package/dist/services/config/Util.js.map +1 -1
  57. package/dist/services/config/constants.js +23 -2
  58. package/dist/services/config/constants.js.map +1 -1
  59. package/dist/services/config/types.js +49 -9
  60. package/dist/services/config/types.js.map +1 -1
  61. package/dist/services/core/Err.js.map +1 -1
  62. package/dist/services/core/Utils.js +107 -32
  63. package/dist/services/core/Utils.js.map +1 -1
  64. package/dist/services/core/websocket/WebSocketManager.js +2 -1
  65. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  66. package/dist/services/core/websocket/types.js.map +1 -1
  67. package/dist/services/index.js +1 -1
  68. package/dist/services/index.js.map +1 -1
  69. package/dist/services/task/Task.js +688 -0
  70. package/dist/services/task/Task.js.map +1 -0
  71. package/dist/services/task/TaskFactory.js +45 -0
  72. package/dist/services/task/TaskFactory.js.map +1 -0
  73. package/dist/services/task/TaskManager.js +728 -527
  74. package/dist/services/task/TaskManager.js.map +1 -1
  75. package/dist/services/task/TaskUtils.js +162 -26
  76. package/dist/services/task/TaskUtils.js.map +1 -1
  77. package/dist/services/task/constants.js +9 -2
  78. package/dist/services/task/constants.js.map +1 -1
  79. package/dist/services/task/dialer.js +78 -0
  80. package/dist/services/task/dialer.js.map +1 -1
  81. package/dist/services/task/digital/Digital.js +77 -0
  82. package/dist/services/task/digital/Digital.js.map +1 -0
  83. package/dist/services/task/state-machine/TaskStateMachine.js +837 -0
  84. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
  85. package/dist/services/task/state-machine/actions.js +543 -0
  86. package/dist/services/task/state-machine/actions.js.map +1 -0
  87. package/dist/services/task/state-machine/constants.js +161 -0
  88. package/dist/services/task/state-machine/constants.js.map +1 -0
  89. package/dist/services/task/state-machine/guards.js +340 -0
  90. package/dist/services/task/state-machine/guards.js.map +1 -0
  91. package/dist/services/task/state-machine/index.js +53 -0
  92. package/dist/services/task/state-machine/index.js.map +1 -0
  93. package/dist/services/task/state-machine/types.js +54 -0
  94. package/dist/services/task/state-machine/types.js.map +1 -0
  95. package/dist/services/task/state-machine/uiControlsComputer.js +553 -0
  96. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
  97. package/dist/services/task/taskDataNormalizer.js +99 -0
  98. package/dist/services/task/taskDataNormalizer.js.map +1 -0
  99. package/dist/services/task/types.js +212 -4
  100. package/dist/services/task/types.js.map +1 -1
  101. package/dist/services/task/voice/Voice.js +1042 -0
  102. package/dist/services/task/voice/Voice.js.map +1 -0
  103. package/dist/services/task/voice/WebRTC.js +149 -0
  104. package/dist/services/task/voice/WebRTC.js.map +1 -0
  105. package/dist/types/cc.d.ts +94 -1
  106. package/dist/types/config.d.ts +6 -0
  107. package/dist/types/constants.d.ts +17 -1
  108. package/dist/types/index.d.ts +21 -6
  109. package/dist/types/metrics/constants.d.ts +21 -1
  110. package/dist/types/services/ApiAiAssistant.d.ts +22 -4
  111. package/dist/types/services/UserPreference.d.ts +118 -0
  112. package/dist/types/services/config/constants.d.ts +21 -0
  113. package/dist/types/services/config/types.d.ts +171 -10
  114. package/dist/types/services/core/Err.d.ts +4 -0
  115. package/dist/types/services/core/Utils.d.ts +33 -13
  116. package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -0
  117. package/dist/types/services/core/websocket/types.d.ts +1 -1
  118. package/dist/types/services/index.d.ts +1 -1
  119. package/dist/types/services/task/Task.d.ts +157 -0
  120. package/dist/types/services/task/TaskFactory.d.ts +12 -0
  121. package/dist/types/services/task/TaskUtils.d.ts +46 -2
  122. package/dist/types/services/task/constants.d.ts +7 -0
  123. package/dist/types/services/task/dialer.d.ts +30 -0
  124. package/dist/types/services/task/digital/Digital.d.ts +22 -0
  125. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +1144 -0
  126. package/dist/types/services/task/state-machine/actions.d.ts +10 -0
  127. package/dist/types/services/task/state-machine/constants.d.ts +107 -0
  128. package/dist/types/services/task/state-machine/guards.d.ts +90 -0
  129. package/dist/types/services/task/state-machine/index.d.ts +13 -0
  130. package/dist/types/services/task/state-machine/types.d.ts +267 -0
  131. package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
  132. package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
  133. package/dist/types/services/task/types.d.ts +603 -66
  134. package/dist/types/services/task/voice/Voice.d.ts +184 -0
  135. package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
  136. package/dist/types/types.d.ts +135 -0
  137. package/dist/types/webex.d.ts +1 -0
  138. package/dist/types.js +118 -2
  139. package/dist/types.js.map +1 -1
  140. package/dist/webex.js +14 -2
  141. package/dist/webex.js.map +1 -1
  142. package/package.json +15 -12
  143. package/src/cc.ts +329 -30
  144. package/src/config.ts +6 -0
  145. package/src/constants.ts +17 -1
  146. package/src/index.ts +23 -5
  147. package/src/metrics/ai-docs/AGENTS.md +350 -0
  148. package/src/metrics/ai-docs/ARCHITECTURE.md +338 -0
  149. package/src/metrics/ai-docs/metrics-spec.md +854 -0
  150. package/src/metrics/behavioral-events.ts +106 -0
  151. package/src/metrics/constants.ts +27 -4
  152. package/src/services/ApiAiAssistant.ts +203 -8
  153. package/src/services/UserPreference.ts +509 -0
  154. package/src/services/agent/ai-docs/AGENTS.md +240 -0
  155. package/src/services/agent/ai-docs/ARCHITECTURE.md +304 -0
  156. package/src/services/agent/ai-docs/agent-spec.md +504 -0
  157. package/src/services/ai-docs/AGENTS.md +386 -0
  158. package/src/services/ai-docs/services-spec.md +492 -0
  159. package/src/services/config/Util.ts +3 -3
  160. package/src/services/config/ai-docs/AGENTS.md +255 -0
  161. package/src/services/config/ai-docs/ARCHITECTURE.md +426 -0
  162. package/src/services/config/ai-docs/config-spec.md +669 -0
  163. package/src/services/config/constants.ts +25 -1
  164. package/src/services/config/types.ts +174 -11
  165. package/src/services/core/Err.ts +2 -0
  166. package/src/services/core/Utils.ts +123 -37
  167. package/src/services/core/ai-docs/AGENTS.md +381 -0
  168. package/src/services/core/ai-docs/ARCHITECTURE.md +698 -0
  169. package/src/services/core/ai-docs/core-spec.md +783 -0
  170. package/src/services/core/websocket/WebSocketManager.ts +2 -0
  171. package/src/services/core/websocket/types.ts +1 -1
  172. package/src/services/index.ts +1 -1
  173. package/src/services/task/Task.ts +837 -0
  174. package/src/services/task/TaskFactory.ts +55 -0
  175. package/src/services/task/TaskManager.ts +739 -614
  176. package/src/services/task/TaskUtils.ts +205 -25
  177. package/src/services/task/ai-docs/AGENTS.md +457 -0
  178. package/src/services/task/ai-docs/ARCHITECTURE.md +594 -0
  179. package/src/services/task/ai-docs/task-spec.md +1319 -0
  180. package/src/services/task/constants.ts +7 -0
  181. package/src/services/task/dialer.ts +80 -0
  182. package/src/services/task/digital/Digital.ts +95 -0
  183. package/src/services/task/state-machine/TaskStateMachine.ts +1077 -0
  184. package/src/services/task/state-machine/actions.ts +685 -0
  185. package/src/services/task/state-machine/ai-docs/AGENTS.md +458 -0
  186. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1137 -0
  187. package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +2115 -0
  188. package/src/services/task/state-machine/constants.ts +172 -0
  189. package/src/services/task/state-machine/guards.ts +406 -0
  190. package/src/services/task/state-machine/index.ts +28 -0
  191. package/src/services/task/state-machine/types.ts +241 -0
  192. package/src/services/task/state-machine/uiControlsComputer.ts +867 -0
  193. package/src/services/task/taskDataNormalizer.ts +137 -0
  194. package/src/services/task/types.ts +710 -71
  195. package/src/services/task/voice/Voice.ts +1267 -0
  196. package/src/services/task/voice/WebRTC.ts +187 -0
  197. package/src/types.ts +166 -2
  198. package/src/utils/AGENTS.md +278 -0
  199. package/src/utils/ai-docs/utils-spec.md +381 -0
  200. package/src/webex.js +2 -0
  201. package/test/unit/spec/cc.ts +343 -23
  202. package/test/unit/spec/logger-proxy.ts +70 -0
  203. package/test/unit/spec/services/ApiAiAssistant.ts +178 -20
  204. package/test/unit/spec/services/UserPreference.ts +401 -0
  205. package/test/unit/spec/services/WebCallingService.ts +7 -1
  206. package/test/unit/spec/services/config/index.ts +30 -30
  207. package/test/unit/spec/services/core/Utils.ts +425 -8
  208. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +66 -40
  209. package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
  210. package/test/unit/spec/services/task/Task.ts +477 -0
  211. package/test/unit/spec/services/task/TaskFactory.ts +62 -0
  212. package/test/unit/spec/services/task/TaskManager.ts +834 -1704
  213. package/test/unit/spec/services/task/TaskUtils.ts +206 -0
  214. package/test/unit/spec/services/task/dialer.ts +190 -0
  215. package/test/unit/spec/services/task/digital/Digital.ts +105 -0
  216. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +1825 -0
  217. package/test/unit/spec/services/task/state-machine/guards.ts +479 -0
  218. package/test/unit/spec/services/task/state-machine/types.ts +18 -0
  219. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +2020 -0
  220. package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
  221. package/test/unit/spec/services/task/voice/Voice.ts +631 -0
  222. package/test/unit/spec/services/task/voice/WebRTC.ts +235 -0
  223. package/umd/contact-center.min.js +2 -2
  224. package/umd/contact-center.min.js.map +1 -1
  225. package/dist/services/task/index.js +0 -1525
  226. package/dist/services/task/index.js.map +0 -1
  227. package/dist/types/services/task/index.d.ts +0 -650
  228. package/src/services/task/index.ts +0 -1801
  229. package/test/unit/spec/services/task/index.ts +0 -2184
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.isEventOfType = isEventOfType;
7
+ var _constants = require("./constants");
8
+ /**
9
+ * Task State Machine Types
10
+ *
11
+ * Type definitions for the XState-based task state machine.
12
+ * These types define states, events, context, and schemas for task lifecycle management.
13
+ */
14
+
15
+ /**
16
+ * Represents a participant in a conference call
17
+ */
18
+
19
+ /**
20
+ * UI Control configuration for the task
21
+ */
22
+
23
+ /**
24
+ * Task state machine context.
25
+ * Only stores data that cannot be derived from state machine state.
26
+ */
27
+
28
+ /**
29
+ * Base event type - all events have a type property
30
+ */
31
+
32
+ /**
33
+ * Event payload mapping - defines the payload for each event type
34
+ */
35
+
36
+ /**
37
+ * Union of all possible event payloads
38
+ */
39
+
40
+ /**
41
+ * Type guard to check event type
42
+ */
43
+ function isEventOfType(event, type) {
44
+ return Boolean(event && event.type === type);
45
+ }
46
+
47
+ /**
48
+ * Recording control state for UI controls computer
49
+ */
50
+
51
+ /**
52
+ * State machine configuration type
53
+ */
54
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_constants","require","isEventOfType","event","type","Boolean"],"sources":["types.ts"],"sourcesContent":["/**\n * Task State Machine Types\n *\n * Type definitions for the XState-based task state machine.\n * These types define states, events, context, and schemas for task lifecycle management.\n */\n\nimport type {AnyStateNodeConfig, ActionFunctionMap, EventObject, ActionArgs} from 'xstate';\nimport {DestinationType, TaskChannelType, TaskData, TaskUIControls, VoiceVariant} from '../types';\nimport {TaskEvent, TaskState} from './constants';\n\n/**\n * Represents a participant in a conference call\n */\nexport interface ConferenceParticipant {\n /** Unique identifier for the participant */\n id: string;\n /** Type of participant (agent, customer, or external party) */\n type: 'AGENT' | 'CUSTOMER' | 'EXTERNAL';\n /** Display name of the participant */\n name?: string;\n /** Timestamp when participant joined the conference */\n joinedAt: Date;\n /** Whether this participant initiated the conference */\n isInitiator: boolean;\n /** Whether this participant can be removed from the conference */\n canBeRemoved: boolean;\n}\n\n/**\n * UI Control configuration for the task\n */\nexport interface UIControlConfig {\n /** Whether end call button is enabled (config option) */\n isEndTaskEnabled: boolean;\n /** Whether end consult button is enabled (config option) */\n isEndConsultEnabled: boolean;\n /** Channel type determines which controls are available */\n channelType: TaskChannelType;\n /** Optional voice channel variant to toggle WebRTC-specific controls */\n voiceVariant?: VoiceVariant;\n /** Whether recording controls should be shown for this task */\n isRecordingEnabled: boolean;\n /** Current agent ID for ownership checks (transfer conference) */\n agentId?: string;\n}\n\n/**\n * Task state machine context.\n * Only stores data that cannot be derived from state machine state.\n */\nexport interface TaskContext {\n taskData: TaskData | null;\n\n // Consult tracking\n consultInitiator: boolean;\n exitingConference: boolean;\n consultFromConference: boolean;\n transferConferenceRequested: boolean;\n consultDestinationType: DestinationType | null;\n consultDestinationAgentId: string | null;\n consultDestinationAgentJoined: boolean;\n consultCallHeld: boolean;\n\n // Recording\n recordingControlsAvailable: boolean;\n recordingInProgress: boolean;\n\n // UI\n uiControlConfig: UIControlConfig;\n uiControls: TaskUIControls;\n}\n\nexport type RecordingStateUpdate = Partial<\n Pick<TaskContext, 'recordingControlsAvailable' | 'recordingInProgress'>\n>;\n\n/**\n * Base event type - all events have a type property\n */\ntype BaseEvent<T extends TaskEvent> = {type: T};\n\n/**\n * Event payload mapping - defines the payload for each event type\n */\ninterface TaskEventPayloadMap {\n [TaskEvent.TASK_INCOMING]: BaseEvent<TaskEvent.TASK_INCOMING> & {\n taskData: TaskData;\n /** Set when campaign preview reservation accept is receieved */\n isCampaignReservationAccept?: boolean;\n };\n [TaskEvent.TASK_OFFERED]: BaseEvent<TaskEvent.TASK_OFFERED> & {taskData: TaskData};\n [TaskEvent.OFFER_CONSULT]: BaseEvent<TaskEvent.OFFER_CONSULT> & {taskData: TaskData};\n [TaskEvent.HYDRATE]: BaseEvent<TaskEvent.HYDRATE> & {taskData: TaskData; agentId?: string};\n [TaskEvent.CONTACT_UPDATED]: BaseEvent<TaskEvent.CONTACT_UPDATED> & {taskData: TaskData};\n [TaskEvent.CONTACT_OWNER_CHANGED]: BaseEvent<TaskEvent.CONTACT_OWNER_CHANGED> & {\n taskData: TaskData;\n };\n [TaskEvent.ASSIGN]: BaseEvent<TaskEvent.ASSIGN> & {taskData: TaskData};\n [TaskEvent.HOLD_INITIATED]: BaseEvent<TaskEvent.HOLD_INITIATED> & {mediaResourceId: string};\n [TaskEvent.HOLD_SUCCESS]: BaseEvent<TaskEvent.HOLD_SUCCESS> & {\n mediaResourceId: string;\n taskData?: TaskData;\n };\n [TaskEvent.HOLD_FAILED]: BaseEvent<TaskEvent.HOLD_FAILED> & {\n reason?: string;\n mediaResourceId: string;\n };\n [TaskEvent.UNHOLD_INITIATED]: BaseEvent<TaskEvent.UNHOLD_INITIATED> & {mediaResourceId: string};\n [TaskEvent.UNHOLD_SUCCESS]: BaseEvent<TaskEvent.UNHOLD_SUCCESS> & {\n mediaResourceId: string;\n taskData?: TaskData;\n };\n [TaskEvent.UNHOLD_FAILED]: BaseEvent<TaskEvent.UNHOLD_FAILED> & {\n reason?: string;\n mediaResourceId: string;\n };\n [TaskEvent.CONSULT]: BaseEvent<TaskEvent.CONSULT> & {\n destination: string;\n destAgentId?: string;\n destinationType: DestinationType;\n };\n [TaskEvent.CONSULT_SUCCESS]: BaseEvent<TaskEvent.CONSULT_SUCCESS> & {taskData?: TaskData};\n [TaskEvent.CONSULT_CREATED]: BaseEvent<TaskEvent.CONSULT_CREATED> & {taskData: TaskData};\n [TaskEvent.CONSULTING_ACTIVE]: BaseEvent<TaskEvent.CONSULTING_ACTIVE> & {\n consultDestinationAgentJoined: boolean;\n taskData?: TaskData;\n };\n [TaskEvent.CONSULT_END]: BaseEvent<TaskEvent.CONSULT_END> & {taskData?: TaskData};\n [TaskEvent.CONSULT_FAILED]: BaseEvent<TaskEvent.CONSULT_FAILED> & {\n reason?: string;\n taskData?: TaskData;\n };\n [TaskEvent.MERGE_TO_CONFERENCE]: BaseEvent<TaskEvent.MERGE_TO_CONFERENCE>;\n [TaskEvent.CONFERENCE_START]: BaseEvent<TaskEvent.CONFERENCE_START> & {\n participants?: ConferenceParticipant[];\n taskData?: TaskData;\n };\n [TaskEvent.CONFERENCE_FAILED]: BaseEvent<TaskEvent.CONFERENCE_FAILED> & {\n reason?: string;\n taskData?: TaskData;\n };\n [TaskEvent.CONFERENCE_END]: BaseEvent<TaskEvent.CONFERENCE_END> & {taskData: TaskData};\n [TaskEvent.TRANSFER_CONFERENCE]: BaseEvent<TaskEvent.TRANSFER_CONFERENCE> & {agentId?: string};\n [TaskEvent.PARTICIPANT_LEAVE]: BaseEvent<TaskEvent.PARTICIPANT_LEAVE> & {\n participantId?: string;\n taskData: TaskData;\n };\n [TaskEvent.EXIT_CONFERENCE]: BaseEvent<TaskEvent.EXIT_CONFERENCE> & {agentId?: string};\n [TaskEvent.EXIT_CONFERENCE_SUCCESS]: BaseEvent<TaskEvent.EXIT_CONFERENCE_SUCCESS> & {\n taskData: TaskData;\n };\n [TaskEvent.EXIT_CONFERENCE_FAILED]: BaseEvent<TaskEvent.EXIT_CONFERENCE_FAILED> & {\n reason?: string;\n };\n [TaskEvent.TRANSFER_CONFERENCE_SUCCESS]: BaseEvent<TaskEvent.TRANSFER_CONFERENCE_SUCCESS> & {\n taskData: TaskData;\n };\n [TaskEvent.TRANSFER_CONFERENCE_FAILED]: BaseEvent<TaskEvent.TRANSFER_CONFERENCE_FAILED> & {\n reason?: string;\n };\n [TaskEvent.RECORDING_STARTED]: BaseEvent<TaskEvent.RECORDING_STARTED> & {taskData: TaskData};\n [TaskEvent.PAUSE_RECORDING]: BaseEvent<TaskEvent.PAUSE_RECORDING> & {taskData: TaskData};\n [TaskEvent.RESUME_RECORDING]: BaseEvent<TaskEvent.RESUME_RECORDING> & {taskData: TaskData};\n [TaskEvent.TRANSFER_SUCCESS]: BaseEvent<TaskEvent.TRANSFER_SUCCESS> & {taskData?: TaskData};\n [TaskEvent.TRANSFER_FAILED]: BaseEvent<TaskEvent.TRANSFER_FAILED> & {\n reason?: string;\n taskData?: TaskData;\n };\n [TaskEvent.WRAPUP_COMPLETE]: BaseEvent<TaskEvent.WRAPUP_COMPLETE> & {taskData?: TaskData};\n [TaskEvent.TASK_WRAPUP]: BaseEvent<TaskEvent.TASK_WRAPUP> & {taskData?: TaskData};\n [TaskEvent.RONA]: BaseEvent<TaskEvent.RONA> & {taskData?: TaskData; reason?: string};\n [TaskEvent.CONTACT_ENDED]: BaseEvent<TaskEvent.CONTACT_ENDED> & {taskData: TaskData};\n [TaskEvent.ASSIGN_FAILED]: BaseEvent<TaskEvent.ASSIGN_FAILED> & {reason?: string};\n [TaskEvent.INVITE_FAILED]: BaseEvent<TaskEvent.INVITE_FAILED> & {reason?: string};\n [TaskEvent.OUTBOUND_FAILED]: BaseEvent<TaskEvent.OUTBOUND_FAILED> & {\n reason?: string;\n taskData?: TaskData;\n };\n [TaskEvent.CAMPAIGN_PREVIEW_ACCEPT_FAILED]: BaseEvent<TaskEvent.CAMPAIGN_PREVIEW_ACCEPT_FAILED> & {\n taskData?: TaskData;\n };\n [TaskEvent.CAMPAIGN_PREVIEW_SKIP_FAILED]: BaseEvent<TaskEvent.CAMPAIGN_PREVIEW_SKIP_FAILED> & {\n taskData?: TaskData;\n };\n [TaskEvent.CAMPAIGN_PREVIEW_REMOVE_FAILED]: BaseEvent<TaskEvent.CAMPAIGN_PREVIEW_REMOVE_FAILED> & {\n taskData?: TaskData;\n };\n [TaskEvent.SWITCH_TO_MAIN_CALL]: BaseEvent<TaskEvent.SWITCH_TO_MAIN_CALL>;\n [TaskEvent.SWITCH_TO_CONSULT]: BaseEvent<TaskEvent.SWITCH_TO_CONSULT>;\n [TaskEvent.ACCEPT]: BaseEvent<TaskEvent.ACCEPT>;\n [TaskEvent.DECLINE]: BaseEvent<TaskEvent.DECLINE>;\n [TaskEvent.END]: BaseEvent<TaskEvent.END> & {taskData?: TaskData};\n [TaskEvent.CTQ_CANCEL]: BaseEvent<TaskEvent.CTQ_CANCEL> & {taskData: TaskData};\n [TaskEvent.CTQ_CANCEL_FAILED]: BaseEvent<TaskEvent.CTQ_CANCEL_FAILED> & {taskData: TaskData};\n}\n\n/**\n * Union of all possible event payloads\n */\nexport type TaskEventPayload = TaskEventPayloadMap[TaskEvent];\n\n/**\n * Type guard to check event type\n */\nexport function isEventOfType<T extends TaskEvent>(\n event: TaskEventPayload | undefined,\n type: T\n): event is TaskEventPayloadMap[T] {\n return Boolean(event && event.type === type);\n}\n\n/**\n * Recording control state for UI controls computer\n */\nexport interface RecordingControlState {\n available: boolean;\n inProgress: boolean;\n}\n\n/**\n * State machine configuration type\n */\nexport interface TaskStateMachineConfig {\n id: string;\n initial: TaskState;\n context: TaskContext;\n states: Record<string, AnyStateNodeConfig>;\n}\n\nexport type TaskActionsMap = ActionFunctionMap<\n TaskContext,\n TaskEventPayload,\n never,\n {type: string; params: undefined},\n never,\n never,\n EventObject\n>;\n\nexport type TaskActionArgs = ActionArgs<TaskContext, TaskEventPayload, TaskEventPayload>;\n"],"mappings":";;;;;;AASA,IAAAA,UAAA,GAAAC,OAAA;AATA;AACA;AACA;AACA;AACA;AACA;;AAMA;AACA;AACA;;AAgBA;AACA;AACA;;AAgBA;AACA;AACA;AACA;;AA2BA;AACA;AACA;;AAGA;AACA;AACA;;AAiHA;AACA;AACA;;AAGA;AACA;AACA;AACO,SAASC,aAAaA,CAC3BC,KAAmC,EACnCC,IAAO,EAC0B;EACjC,OAAOC,OAAO,CAACF,KAAK,IAAIA,KAAK,CAACC,IAAI,KAAKA,IAAI,CAAC;AAC9C;;AAEA;AACA;AACA;;AAMA;AACA;AACA","ignoreList":[]}
@@ -0,0 +1,553 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.computeUIControls = computeUIControls;
7
+ exports.getDefaultUIControls = getDefaultUIControls;
8
+ exports.haveUIControlsChanged = haveUIControlsChanged;
9
+ var _types = require("../types");
10
+ var _constants = require("./constants");
11
+ var _TaskUtils = require("../TaskUtils");
12
+ /**
13
+ * UI Controls Computer - Centralized logic for computing UI control states
14
+ */
15
+
16
+ const DISABLED = {
17
+ isVisible: false,
18
+ isEnabled: false
19
+ };
20
+ const VISIBLE_ENABLED = {
21
+ isVisible: true,
22
+ isEnabled: true
23
+ };
24
+ const VISIBLE_DISABLED = {
25
+ isVisible: true,
26
+ isEnabled: false
27
+ };
28
+ function getDefaultInteractionUIControls() {
29
+ return {
30
+ accept: DISABLED,
31
+ decline: DISABLED,
32
+ hold: DISABLED,
33
+ mute: DISABLED,
34
+ end: DISABLED,
35
+ transfer: DISABLED,
36
+ consult: DISABLED,
37
+ consultTransfer: DISABLED,
38
+ endConsult: DISABLED,
39
+ recording: DISABLED,
40
+ conference: DISABLED,
41
+ wrapup: DISABLED,
42
+ exitConference: DISABLED,
43
+ transferConference: DISABLED,
44
+ mergeToConference: DISABLED,
45
+ switch: DISABLED
46
+ };
47
+ }
48
+ function createTaskUIControls(main, consult, activeLeg) {
49
+ return {
50
+ main,
51
+ consult,
52
+ activeLeg
53
+ };
54
+ }
55
+ function getDefaultUIControls() {
56
+ return createTaskUIControls(getDefaultInteractionUIControls(), getDefaultInteractionUIControls(), 'main');
57
+ }
58
+
59
+ /** Consult media must exist on the interaction payload, not only as a stale resource id. */
60
+ function hasConsultMediaInInteraction(interaction, consultMediaResourceId) {
61
+ const media = interaction?.media ?? {};
62
+ const hasConsultLeg = Object.values(media).some(entry => entry?.mType === 'consult');
63
+ if (hasConsultLeg) return true;
64
+ if (!consultMediaResourceId) return false;
65
+ return Boolean(media[consultMediaResourceId]);
66
+ }
67
+ function computeVoiceInteractionUIControls(state, context, config, fallbackTaskData, currentLeg = 'main') {
68
+ // Early exit for idle
69
+ if (state === _constants.TaskState.IDLE) {
70
+ return getDefaultInteractionUIControls();
71
+ }
72
+
73
+ // Prefer live task.data (fallback) over stale state-machine snapshot during multi-login sync.
74
+ const taskData = fallbackTaskData ?? context.taskData ?? null;
75
+ const interaction = taskData?.interaction;
76
+ const mainCallId = interaction?.mainInteractionId || taskData?.interactionId;
77
+ const isWebrtc = config.voiceVariant === _types.VOICE_VARIANT.WEBRTC;
78
+ const isOutdial = interaction?.outboundType === 'OUTDIAL';
79
+ const serverHold = (0, _TaskUtils.getServerHoldStateForControls)(context, mainCallId, fallbackTaskData);
80
+
81
+ // Backend-derived checks
82
+ const customerInCall = interaction && mainCallId ? (0, _TaskUtils.getIsCustomerInCall)(interaction, mainCallId) : false;
83
+ // EP-DN/secondary legs can have incomplete media participant lists; fall back to participants map.
84
+ const customerPresent = customerInCall || Boolean(interaction && interaction.participants && Object.values(interaction.participants).some(p => p?.pType === 'Customer' && !p?.hasLeft));
85
+ const participantCount = interaction && mainCallId ? (0, _TaskUtils.getConferenceParticipantsCount)(interaction, mainCallId) : 0;
86
+ const maxParticipants = participantCount >= _constants.MAX_PARTICIPANTS_IN_MULTIPARTY_CONFERENCE;
87
+ const selfAgentId = config.agentId ?? taskData?.agentId;
88
+ const consultInProgress = (0, _TaskUtils.getIsConsultInProgressForConferenceControls)(interaction, mainCallId, selfAgentId);
89
+ const conferenceFromBackend = taskData ? (0, _TaskUtils.getIsConferenceInProgress)(taskData) : false;
90
+ // Note: ownership is used by some controls; keep computations local to those controls
91
+
92
+ // Context flags (set by state machine actions)
93
+ const {
94
+ consultInitiator,
95
+ consultDestinationAgentJoined,
96
+ consultDestinationType,
97
+ consultCallHeld,
98
+ consultFromConference
99
+ } = context;
100
+ const {
101
+ recordingControlsAvailable
102
+ } = context;
103
+
104
+ // EP_DN consults are "ready" as soon as the consult is created (EP accepts routing immediately).
105
+ // Backend sends destinationType as 'EP-DN'; SDK method uses 'entryPoint' — check both.
106
+ const isEpDnConsult = consultDestinationType === 'entryPoint' || consultDestinationType === 'EP-DN';
107
+ const stateImpliesHeld = state === _constants.TaskState.HELD || state === _constants.TaskState.RESUME_INITIATING;
108
+ const stateImpliesConnected = state === _constants.TaskState.CONNECTED || state === _constants.TaskState.HOLD_INITIATING;
109
+ const isHeld = stateImpliesHeld || serverHold === true;
110
+ const isConnected = stateImpliesConnected || !stateImpliesHeld && serverHold === false;
111
+
112
+ // State categories for cleaner logic
113
+ const isConsulting = state === _constants.TaskState.CONSULTING || state === _constants.TaskState.CONSULT_INITIATING || state === _constants.TaskState.CONF_INITIATING;
114
+ const isConferencing = state === _constants.TaskState.CONFERENCING;
115
+ const isWrappingUp = state === _constants.TaskState.WRAPPING_UP;
116
+ const selfInMainCall = Boolean(selfAgentId) && Boolean(mainCallId) && Boolean(interaction?.media?.[mainCallId]?.participants?.includes(selfAgentId));
117
+ const consultMedia = Object.values(interaction?.media ?? {}).find(media => media?.mediaResourceId === taskData?.consultMediaResourceId || media?.mType === 'consult');
118
+ const selfParticipant = selfAgentId ? interaction?.participants?.[selfAgentId] : null;
119
+ const selfInConsultCall = Boolean(selfAgentId) && Boolean(consultMedia?.participants?.includes(selfAgentId));
120
+ const hasConsultMedia = hasConsultMediaInInteraction(interaction, taskData?.consultMediaResourceId);
121
+ const isConsultEndedForSelf = taskData?.type === 'AgentConsultEnded' || taskData?.type === 'AgentConsultFailed' || selfParticipant?.consultState === 'consultCompleted' && !hasConsultMedia && taskData?.isConsulted === false;
122
+ const effectiveConsultInitiator = isConsultEndedForSelf ? false : consultInitiator;
123
+ const effectiveConsultCallHeld = isConsultEndedForSelf ? false : consultCallHeld;
124
+ const effectiveConsultFromConference = isConsultEndedForSelf ? false : consultFromConference;
125
+ const effectiveConsultDestinationAgentJoined = isConsultEndedForSelf ? false : consultDestinationAgentJoined;
126
+ // After a consult ends for self, the backend stops sending consult media but the merged
127
+ // task.data can still carry the stale consult-media entry (reconcileData never deletes keys).
128
+ // Treat it as gone so post-consult main-leg controls (consult retry) are not blocked.
129
+ const effectiveHasConsultMedia = isConsultEndedForSelf ? false : hasConsultMedia;
130
+ const isConsultDestinationReady = effectiveConsultDestinationAgentJoined || isEpDnConsult;
131
+ const conferenceActive = isConferencing || conferenceFromBackend || effectiveConsultFromConference;
132
+ // Treat consult initiator as "in conference" even if mainCall participant list lags while consulting.
133
+ const inConference = conferenceActive && (isConferencing || selfInMainCall || effectiveConsultInitiator);
134
+
135
+ // Check if this is a consulted agent (must be after isConsulting is computed).
136
+ const isSoleAgentOnCall = participantCount <= 1 && selfInMainCall && !isConsulting && !inConference;
137
+ const isConsulted = inConference || isSoleAgentOnCall ? false : (0, _TaskUtils.getIsConsultedAgentForControls)(taskData, context, isConsulting) || !effectiveConsultInitiator && (selfParticipant?.isConsulted === true || selfParticipant?.consultState === 'consulting');
138
+
139
+ // Active call = can perform call operations
140
+ const isActive = state === _constants.TaskState.CONNECTED || state === _constants.TaskState.HELD || state === _constants.TaskState.HOLD_INITIATING || state === _constants.TaskState.RESUME_INITIATING || isConsulting || isConferencing;
141
+
142
+ // Consulted agents have limited controls until they're in conference or wrapup
143
+ // Use inConference (not isConferencing) so controls remain enabled after state downgrade
144
+ const hasFullControls = !isConsulted || effectiveConsultInitiator || inConference || isWrappingUp;
145
+ const consultOwnedBySelf = effectiveConsultInitiator || Boolean(selfAgentId) && taskData?.consultingAgentId === selfAgentId;
146
+ const selfConsultPendingOnConsultMedia = selfParticipant?.consultState === 'consultInitiated' && !taskData?.isConsulted && hasConsultMedia;
147
+ const ownerParticipant = interaction?.owner ? interaction.participants?.[interaction.owner] : undefined;
148
+ const otherAgentConsultInProgress = Boolean(interaction?.participants && Object.values(interaction.participants).some(participant => {
149
+ if (!participant || participant.hasLeft) return false;
150
+ if (participant.id === selfAgentId) return false;
151
+ if (participant.pType !== 'AGENT') return false;
152
+ return participant.consultState === 'consultInitiated' || participant.consultState === 'consultReserved' || participant.consultState === 'consulting' || participant.currentState === 'consulting';
153
+ }));
154
+ const isHydratedConferenceConsultPending = inConference && selfConsultPendingOnConsultMedia && !effectiveConsultDestinationAgentJoined;
155
+ const hasParallelConsultLeg = !isConsultEndedForSelf && consultOwnedBySelf && !isConsulting && !isConsulted && (consultInProgress || effectiveConsultCallHeld || hasConsultMedia);
156
+ const activeLegForConferenceConsult = effectiveConsultCallHeld ? 'main' : 'consult';
157
+ const isCurrentLegActive = currentLeg === activeLegForConferenceConsult;
158
+ const isConferenceConsultTransferContext = inConference && effectiveConsultInitiator && hasConsultMedia && isConsultDestinationReady;
159
+ const consultLegOnHold = isConsulting && effectiveConsultCallHeld;
160
+ const callProcessingDetails = interaction?.callProcessingDetails;
161
+ const conferenceHoldParticipant = callProcessingDetails?.conferenceHoldParticipant === true || callProcessingDetails?.conferenceHoldParticipant === 'true';
162
+ const postDeclineHeldMainLeg = consultInitiator && !consultDestinationAgentJoined && isHeld && inConference && conferenceHoldParticipant;
163
+ const postConsultCompletedHeldMainLeg = selfParticipant?.consultState === 'consultCompleted' && isHeld && inConference && !isConsulting;
164
+ const nonOwnerPostConsultCompletedHeldMainLeg = isHeld && inConference && !isConsulting && !consultInitiator && Boolean(selfAgentId) && Boolean(interaction?.owner) && selfAgentId !== interaction?.owner && ownerParticipant?.consultState === 'consultCompleted';
165
+ const isConsultPendingBeforeJoin = selfParticipant?.consultState === 'consultInitiated' && !consultDestinationAgentJoined;
166
+ const hideExitConferenceWhileConsultPending = currentLeg === 'main' && inConference && isConsultPendingBeforeJoin && (consultFromConference || consultInitiator || taskData?.type === 'AgentConsultCreated' || consultInProgress || isConsulting);
167
+ const hideExitConferenceDuringActiveConsultFromConference = inConference && consultInitiator && consultDestinationAgentJoined && (isConsulting || taskData?.type === 'AgentConsulting' || selfParticipant?.consultState === 'consulting');
168
+ const hideExitConferenceOnMainLegForEpDnConsultFromConference = currentLeg === 'main' && inConference && consultFromConference && consultInitiator && (isConsulting || consultInProgress || taskData?.type === 'AgentConsultCreated' || taskData?.type === 'AgentConsulting' || selfParticipant?.consultState === 'consultInitiated' || selfParticipant?.consultState === 'consulting');
169
+ const forceHeldPostConsultControls = !hideExitConferenceWhileConsultPending && (postDeclineHeldMainLeg || postConsultCompletedHeldMainLeg);
170
+ const selfOnConsultLeg = selfParticipant?.consultState === 'consulting' || selfParticipant?.currentState === 'consulting';
171
+ const showMainLegConferenceControlsDuringConsult = currentLeg === 'main' && inConference && consultInProgress && !selfOnConsultLeg;
172
+ const allowHeldMainLegControlsForNonInitiator = showMainLegConferenceControlsDuringConsult && !isHydratedConferenceConsultPending;
173
+ const isConsultRequestedPhase = isConsultPendingBeforeJoin && !isConsulted && (consultInitiator || Boolean(selfAgentId) && selfParticipant?.consultState === 'consultInitiated' && taskData?.agentId === selfAgentId);
174
+ const isConsultUnansweredFailure = currentLeg === 'main' && !inConference && !taskData?.isConsulted && !effectiveHasConsultMedia && isHeld && selfParticipant?.consultState === 'consultCompleted' && (taskData?.type === 'AgentConsultFailed' || taskData?.type === 'AgentConsultEnded' || isConsultEndedForSelf);
175
+ if (isConsultUnansweredFailure) {
176
+ const recordingControl = recordingControlsAvailable && config.isRecordingEnabled && !inConference ? VISIBLE_ENABLED : DISABLED;
177
+ return {
178
+ ...getDefaultInteractionUIControls(),
179
+ hold: VISIBLE_ENABLED,
180
+ transfer: VISIBLE_ENABLED,
181
+ consult: VISIBLE_ENABLED,
182
+ recording: recordingControl,
183
+ end: VISIBLE_DISABLED
184
+ };
185
+ }
186
+ if (isConsultRequestedPhase) {
187
+ if (currentLeg === 'main') {
188
+ return {
189
+ ...getDefaultInteractionUIControls(),
190
+ transfer: VISIBLE_DISABLED,
191
+ conference: VISIBLE_DISABLED,
192
+ end: VISIBLE_DISABLED
193
+ };
194
+ }
195
+ if (currentLeg === 'consult') {
196
+ return {
197
+ ...getDefaultInteractionUIControls(),
198
+ endConsult: VISIBLE_ENABLED,
199
+ switch: VISIBLE_DISABLED,
200
+ transfer: VISIBLE_DISABLED,
201
+ transferConference: inConference ? VISIBLE_DISABLED : DISABLED,
202
+ mergeToConference: VISIBLE_DISABLED
203
+ };
204
+ }
205
+ }
206
+ return {
207
+ // Accept/Decline: Voice tasks in offered state
208
+ // Desktop/WebRTC + inbound: accept enabled (agent manually accepts)
209
+ // Desktop/WebRTC + outdial: accept disabled (auto-answer handles it; Widgets show "Accept" disabled)
210
+ // Extension mode (non-WebRTC): accept disabled (Widgets show "Ringing...")
211
+ accept: state === _constants.TaskState.OFFERED && !interaction?.isTerminated ? {
212
+ isVisible: true,
213
+ isEnabled: isWebrtc && !isOutdial
214
+ } : DISABLED,
215
+ decline: (() => {
216
+ if (!isWebrtc || state !== _constants.TaskState.OFFERED || interaction?.isTerminated) return DISABLED;
217
+ return isOutdial ? VISIBLE_DISABLED : VISIBLE_ENABLED;
218
+ })(),
219
+ // Hold: visible in connected/held/conference, disabled in conference/consulting
220
+ hold: (() => {
221
+ if (!hasFullControls) return DISABLED;
222
+ if (forceHeldPostConsultControls) return VISIBLE_ENABLED;
223
+ if (consultOwnedBySelf && (isConsulting || hasParallelConsultLeg || effectiveConsultCallHeld)) {
224
+ return DISABLED;
225
+ }
226
+ if (hasParallelConsultLeg) return DISABLED;
227
+ if (state === _constants.TaskState.OFFERED) return DISABLED;
228
+ if (isWrappingUp) return DISABLED;
229
+ // Visibility: connected || held || inConference
230
+ if (!(isConnected || isHeld || inConference)) return DISABLED;
231
+ // Enabled: (connected || held) && !inConference && !isConsulting
232
+ const canHold = (isConnected || isHeld) && !inConference && !isConsulting;
233
+ return canHold ? VISIBLE_ENABLED : VISIBLE_DISABLED;
234
+ })(),
235
+ // Mute: WebRTC only, active calls; hidden entirely during wrapup
236
+ mute: (() => {
237
+ if (!isWebrtc) return DISABLED;
238
+ if (isWrappingUp) return DISABLED;
239
+ if (currentLeg === 'consult' && !selfInConsultCall) return DISABLED;
240
+ // The consulted agent has no separate consult leg; their main leg is the active call, so
241
+ // mute stays available while they consult (the heuristic below targets the initiator's
242
+ // inactive leg, not the consultee's only leg).
243
+ const isConsultedActiveMainLeg = isConsulted && currentLeg === 'main';
244
+ if ((isConsulting || hasParallelConsultLeg) && !isCurrentLegActive && !isConsultedActiveMainLeg) {
245
+ return VISIBLE_DISABLED;
246
+ }
247
+ if (isConsulting) return VISIBLE_ENABLED;
248
+ if (isConnected || isHeld || isConferencing) {
249
+ if (inConference) return VISIBLE_ENABLED;
250
+ return isHeld ? VISIBLE_DISABLED : VISIBLE_ENABLED;
251
+ }
252
+ return DISABLED;
253
+ })(),
254
+ // End: varies by state; during consulting only on main leg (consult held)
255
+ end: (() => {
256
+ if (allowHeldMainLegControlsForNonInitiator) return VISIBLE_ENABLED;
257
+ if (showMainLegConferenceControlsDuringConsult) return VISIBLE_DISABLED;
258
+ if (isHydratedConferenceConsultPending && currentLeg === 'main') return VISIBLE_DISABLED;
259
+ if (!config.isEndTaskEnabled && !isWebrtc) return DISABLED;
260
+ if (hasParallelConsultLeg) {
261
+ return isConnected && isEpDnConsult ? VISIBLE_ENABLED : VISIBLE_DISABLED;
262
+ }
263
+ if (isConsulting) {
264
+ if (currentLeg === 'consult' && consultCallHeld) return DISABLED;
265
+ return consultInitiator && consultCallHeld ? VISIBLE_ENABLED : DISABLED;
266
+ }
267
+ if (inConference) {
268
+ if (isConsulted) return DISABLED;
269
+ if (forceHeldPostConsultControls) return VISIBLE_DISABLED;
270
+ if (consultInProgress) return VISIBLE_DISABLED;
271
+ return isWrappingUp ? VISIBLE_DISABLED : VISIBLE_ENABLED;
272
+ }
273
+ if (!hasFullControls) return DISABLED;
274
+ if (isActive) return isHeld || isWrappingUp ? VISIBLE_DISABLED : VISIBLE_ENABLED;
275
+ return DISABLED;
276
+ })(),
277
+ // Transfer: connected/held/conference
278
+ transfer: (() => {
279
+ if (isHydratedConferenceConsultPending) return VISIBLE_DISABLED;
280
+ if (isConferenceConsultTransferContext && currentLeg === 'main' && isCurrentLegActive) {
281
+ return DISABLED;
282
+ }
283
+ if (inConference && isConsulting && consultInitiator) return DISABLED;
284
+ if (hasParallelConsultLeg) {
285
+ if (!customerPresent) return DISABLED;
286
+ if (state === _constants.TaskState.CONNECTED) return VISIBLE_ENABLED;
287
+ if (state === _constants.TaskState.HELD) return VISIBLE_DISABLED;
288
+ }
289
+ if (isConsulting) {
290
+ if (!consultInitiator) return DISABLED;
291
+ if (!customerPresent) return VISIBLE_DISABLED;
292
+ if (consultLegOnHold) return VISIBLE_DISABLED;
293
+ return isConsultDestinationReady ? VISIBLE_ENABLED : VISIBLE_DISABLED;
294
+ }
295
+ if (!hasFullControls) return DISABLED;
296
+ if (inConference) {
297
+ // Real conference (multiple agents): transfer is hidden
298
+ // Pending conference (only self agent): transfer remains available
299
+ return participantCount > 1 ? DISABLED : VISIBLE_ENABLED;
300
+ }
301
+ if (state === _constants.TaskState.CONNECTED || state === _constants.TaskState.HELD) return VISIBLE_ENABLED;
302
+ return DISABLED;
303
+ })(),
304
+ // Consult: connected/held/conference when conditions met
305
+ consult: (() => {
306
+ const isConnectedOrHeld = state === _constants.TaskState.CONNECTED || state === _constants.TaskState.HELD;
307
+ if (isHeld && !inConference && !effectiveHasConsultMedia && selfParticipant?.consultState === 'consultCompleted' && !taskData?.isConsulted) {
308
+ const canRetryConsult = !maxParticipants && customerPresent && !otherAgentConsultInProgress;
309
+ return {
310
+ isVisible: true,
311
+ isEnabled: canRetryConsult
312
+ };
313
+ }
314
+ if (inConference && nonOwnerPostConsultCompletedHeldMainLeg) return VISIBLE_DISABLED;
315
+ if (hasParallelConsultLeg) return DISABLED;
316
+ if (!hasFullControls || !(isConnectedOrHeld || inConference)) {
317
+ return DISABLED;
318
+ }
319
+
320
+ // In conference: behavior depends on whether it's a real multi-agent conference
321
+ if (inConference) {
322
+ // Pending conference (only self agent): consult disabled
323
+ if (participantCount <= 1) return VISIBLE_DISABLED;
324
+ // Real conference: consult enabled if conditions met
325
+ const canFromConference = !maxParticipants && customerPresent && !consultInProgress && !otherAgentConsultInProgress && !isConsulting;
326
+ return {
327
+ isVisible: true,
328
+ isEnabled: canFromConference
329
+ };
330
+ }
331
+
332
+ // Enabled conditions for connected/held
333
+ const canFromConnected = !maxParticipants && customerPresent && !consultInProgress && !isConsulted;
334
+ return {
335
+ isVisible: true,
336
+ isEnabled: canFromConnected
337
+ };
338
+ })(),
339
+ // ConsultTransfer: always hidden (use transfer button)
340
+ consultTransfer: DISABLED,
341
+ // EndConsult: during consulting
342
+ endConsult: (() => {
343
+ if (!isConsulting) return DISABLED;
344
+ if (isConsulted && isConferencing) return DISABLED;
345
+ if (!isConsulted && isConferencing && !(consultInitiator && conferenceFromBackend)) {
346
+ return DISABLED;
347
+ }
348
+ return {
349
+ isVisible: true,
350
+ isEnabled: consultInitiator || config.isEndConsultEnabled
351
+ };
352
+ })(),
353
+ // Recording: connected/held, hidden in real conference, visible in pending conference
354
+ recording: (() => {
355
+ if (!recordingControlsAvailable || !config.isRecordingEnabled) return DISABLED;
356
+ if (!hasFullControls || isConsulting) return DISABLED;
357
+ if (inConference) {
358
+ // Real conference (multiple agents): recording hidden
359
+ // Pending conference (only self agent): recording available
360
+ return participantCount > 1 ? DISABLED : VISIBLE_ENABLED;
361
+ }
362
+ if (hasParallelConsultLeg && !customerPresent) return DISABLED;
363
+ if (state === _constants.TaskState.CONNECTED || state === _constants.TaskState.HELD) {
364
+ return VISIBLE_ENABLED;
365
+ }
366
+ return DISABLED;
367
+ })(),
368
+ // Conference: during consulting, enabled on both legs when agent joined
369
+ // Label changes based on leg: "Conference" on main leg, "Merge" on consult leg
370
+ conference: (() => {
371
+ if (isHydratedConferenceConsultPending && currentLeg === 'main') return VISIBLE_DISABLED;
372
+ if (hasParallelConsultLeg) {
373
+ if (!customerPresent) return DISABLED;
374
+ if (state === _constants.TaskState.CONNECTED) {
375
+ return maxParticipants ? VISIBLE_DISABLED : VISIBLE_ENABLED;
376
+ }
377
+ if (state === _constants.TaskState.HELD) return VISIBLE_DISABLED;
378
+ return DISABLED;
379
+ }
380
+ if (!hasFullControls || !isConsulting) return DISABLED;
381
+ if (!consultInitiator) return DISABLED;
382
+ if (!customerPresent) return VISIBLE_DISABLED;
383
+ if (consultLegOnHold) return VISIBLE_DISABLED;
384
+ return isConsultDestinationReady && !maxParticipants ? VISIBLE_ENABLED : VISIBLE_DISABLED;
385
+ })(),
386
+ // Wrapup: wrapping up state
387
+ wrapup: isWrappingUp ? VISIBLE_ENABLED : DISABLED,
388
+ // ExitConference: in conference with multiple agents in main call
389
+ exitConference: (() => {
390
+ if (hideExitConferenceWhileConsultPending) return DISABLED;
391
+ if (hideExitConferenceOnMainLegForEpDnConsultFromConference) return DISABLED;
392
+ if (allowHeldMainLegControlsForNonInitiator) return VISIBLE_ENABLED;
393
+ if (showMainLegConferenceControlsDuringConsult) return VISIBLE_DISABLED;
394
+ if (hideExitConferenceDuringActiveConsultFromConference) return DISABLED;
395
+ if (forceHeldPostConsultControls) return VISIBLE_DISABLED;
396
+ if (isConsulted && !isConferencing) return DISABLED;
397
+ if (!inConference) return DISABLED;
398
+ if (participantCount <= 1) return DISABLED;
399
+ if (consultInProgress) return VISIBLE_DISABLED;
400
+ const consultingFromConference = consultInitiator && isConsulting && conferenceFromBackend;
401
+ return consultingFromConference ? VISIBLE_DISABLED : VISIBLE_ENABLED;
402
+ })(),
403
+ // TransferConference: in conference with active consult, owner consulting from conference
404
+ transferConference: (() => {
405
+ if (isConferenceConsultTransferContext && !isCurrentLegActive) return VISIBLE_DISABLED;
406
+ const consultLegTransferAvailable = currentLeg === 'consult' && inConference && consultInitiator && hasConsultMedia;
407
+ const selfConsultingOnParticipantState = selfParticipant?.consultState === 'consulting';
408
+ const conferenceTransferAvailable = consultLegTransferAvailable || inConference && consultInitiator && hasConsultMedia && (isConsulting || consultInProgress || selfConsultingOnParticipantState);
409
+ if (consultLegOnHold) return DISABLED;
410
+ if (hasParallelConsultLeg && !conferenceTransferAvailable) return DISABLED;
411
+ if (!conferenceTransferAvailable && (!inConference || !isConsulting)) return DISABLED;
412
+ if (!consultInitiator || isConsulted) return DISABLED;
413
+ return isConsultDestinationReady ? VISIBLE_ENABLED : VISIBLE_DISABLED;
414
+ })(),
415
+ // MergeToConference: mirrors conference control, enabled on both legs
416
+ mergeToConference: (() => {
417
+ if (isHydratedConferenceConsultPending && currentLeg === 'consult') return VISIBLE_DISABLED;
418
+ if (!isConsulting || !consultInitiator) return DISABLED;
419
+ if (!customerPresent) return VISIBLE_DISABLED;
420
+ if (consultLegOnHold) return VISIBLE_DISABLED;
421
+ return isConsultDestinationReady && !maxParticipants ? VISIBLE_ENABLED : VISIBLE_DISABLED;
422
+ })(),
423
+ // Switch: visible only on the currently active leg
424
+ switch: (() => {
425
+ if (isHydratedConferenceConsultPending && currentLeg === 'consult') return VISIBLE_DISABLED;
426
+ if (!customerPresent && hasParallelConsultLeg) return DISABLED;
427
+ if (currentLeg === 'consult') {
428
+ if (!isConsulting || !consultInitiator || consultCallHeld) return DISABLED;
429
+ if (!customerPresent) return VISIBLE_DISABLED;
430
+ return isConsultDestinationReady ? VISIBLE_ENABLED : VISIBLE_DISABLED;
431
+ }
432
+ if (hasParallelConsultLeg && state === _constants.TaskState.CONNECTED) {
433
+ return isConsultDestinationReady ? VISIBLE_ENABLED : VISIBLE_DISABLED;
434
+ }
435
+ return DISABLED;
436
+ })()
437
+ };
438
+ }
439
+ function computeDigitalInteractionUIControls(state, context, fallbackTaskData) {
440
+ const taskData = context.taskData ?? fallbackTaskData ?? null;
441
+ const isTerminated = taskData?.interaction?.isTerminated ?? false;
442
+ const isConnected = state === _constants.TaskState.CONNECTED;
443
+ const isWrappingUp = state === _constants.TaskState.WRAPPING_UP;
444
+ return {
445
+ accept: state === _constants.TaskState.OFFERED ? VISIBLE_ENABLED : DISABLED,
446
+ decline: DISABLED,
447
+ hold: DISABLED,
448
+ mute: DISABLED,
449
+ end: isConnected && !isWrappingUp ? VISIBLE_ENABLED : DISABLED,
450
+ transfer: isConnected && !isWrappingUp ? VISIBLE_ENABLED : DISABLED,
451
+ consult: DISABLED,
452
+ consultTransfer: DISABLED,
453
+ endConsult: DISABLED,
454
+ recording: DISABLED,
455
+ conference: DISABLED,
456
+ wrapup: isTerminated || isWrappingUp ? VISIBLE_ENABLED : DISABLED,
457
+ exitConference: DISABLED,
458
+ transferConference: DISABLED,
459
+ mergeToConference: DISABLED,
460
+ switch: DISABLED
461
+ };
462
+ }
463
+ function getVoiceLegState(currentState, context, config, fallbackTaskData) {
464
+ if (currentState === _constants.TaskState.WRAPPING_UP) {
465
+ return {
466
+ hasConsultLeg: false,
467
+ activeLeg: 'main',
468
+ mainState: currentState,
469
+ consultState: _constants.TaskState.CONSULTING
470
+ };
471
+ }
472
+ const taskData = fallbackTaskData ?? context.taskData ?? null;
473
+ const interaction = taskData?.interaction;
474
+ const mainCallId = interaction?.mainInteractionId || taskData?.interactionId;
475
+ const selfAgentId = config.agentId ?? taskData?.agentId;
476
+ const selfParticipant = selfAgentId ? interaction?.participants?.[selfAgentId] : null;
477
+ const hasConsultMedia = hasConsultMediaInInteraction(interaction, taskData?.consultMediaResourceId);
478
+ const isConsultEndedForSelf = taskData?.type === 'AgentConsultEnded' || taskData?.type === 'AgentConsultFailed' || selfParticipant?.consultState === 'consultCompleted' && !hasConsultMedia && taskData?.isConsulted === false;
479
+ const consultInProgress = (0, _TaskUtils.getIsConsultInProgressForConferenceControls)(interaction, mainCallId, selfAgentId);
480
+ const isConsultingState = currentState === _constants.TaskState.CONSULTING || currentState === _constants.TaskState.CONSULT_INITIATING || currentState === _constants.TaskState.CONF_INITIATING;
481
+ const consultOwnedBySelf = context.consultInitiator || Boolean(selfAgentId) && taskData?.consultingAgentId === selfAgentId;
482
+ const selfConsultPendingOnConsultMedia = selfParticipant?.consultState === 'consultInitiated' && !taskData?.isConsulted && hasConsultMedia;
483
+ const selfConsultingOnConsultMedia = selfParticipant?.consultState === 'consulting' && hasConsultMedia;
484
+ const isConsultUnansweredFailure = !taskData?.isConsulted && !hasConsultMedia && selfParticipant?.consultState === 'consultCompleted' && Boolean(mainCallId && taskData?.interaction?.media?.[mainCallId]?.isHold === true) && taskData?.interaction?.state !== 'conference' && !(0, _TaskUtils.getIsConferenceInProgress)(taskData);
485
+ const hasConsultLeg = Boolean(!isConsultEndedForSelf && !interaction?.isTerminated && !isConsultUnansweredFailure && (consultOwnedBySelf || selfConsultingOnConsultMedia || selfConsultPendingOnConsultMedia) && (consultInProgress || isConsultingState || context.consultCallHeld || hasConsultMedia));
486
+ if (!hasConsultLeg) {
487
+ let mainState = currentState;
488
+ if (isConsultEndedForSelf && taskData?.interaction) {
489
+ const resolvedMainId = taskData.interaction.mainInteractionId || taskData.interactionId;
490
+ const isMainHeld = Boolean(resolvedMainId && taskData.interaction.media?.[resolvedMainId]?.isHold === true);
491
+ if (taskData.interaction.state === 'conference' || (0, _TaskUtils.getIsConferenceInProgress)(taskData)) {
492
+ mainState = _constants.TaskState.CONFERENCING;
493
+ } else if (isMainHeld || taskData.interaction.state === 'hold') {
494
+ mainState = _constants.TaskState.HELD;
495
+ } else {
496
+ mainState = _constants.TaskState.CONNECTED;
497
+ }
498
+ }
499
+ return {
500
+ hasConsultLeg: false,
501
+ activeLeg: 'main',
502
+ mainState,
503
+ consultState: _constants.TaskState.CONSULTING
504
+ };
505
+ }
506
+ let mainState = _constants.TaskState.HELD;
507
+ if (currentState === _constants.TaskState.CONFERENCING) {
508
+ mainState = _constants.TaskState.CONFERENCING;
509
+ } else if (context.consultCallHeld) {
510
+ mainState = _constants.TaskState.CONNECTED;
511
+ }
512
+ return {
513
+ hasConsultLeg: true,
514
+ activeLeg: context.consultCallHeld ? 'main' : 'consult',
515
+ mainState,
516
+ consultState: isConsultingState ? currentState : _constants.TaskState.CONSULTING
517
+ };
518
+ }
519
+ function computeUIControls(currentState, context, fallbackTaskData) {
520
+ if (currentState === _constants.TaskState.TERMINATED || currentState === _constants.TaskState.COMPLETED) {
521
+ return getDefaultUIControls();
522
+ }
523
+ switch (context.uiControlConfig.channelType) {
524
+ case _types.TASK_CHANNEL_TYPE.VOICE:
525
+ {
526
+ const {
527
+ hasConsultLeg,
528
+ activeLeg,
529
+ mainState,
530
+ consultState
531
+ } = getVoiceLegState(currentState, context, context.uiControlConfig, fallbackTaskData);
532
+ const mainControls = computeVoiceInteractionUIControls(mainState, context, context.uiControlConfig, fallbackTaskData, 'main');
533
+ const consultControls = hasConsultLeg ? computeVoiceInteractionUIControls(consultState, context, context.uiControlConfig, fallbackTaskData, 'consult') : getDefaultInteractionUIControls();
534
+ return createTaskUIControls(mainControls, consultControls, activeLeg);
535
+ }
536
+ case _types.TASK_CHANNEL_TYPE.DIGITAL:
537
+ return createTaskUIControls(computeDigitalInteractionUIControls(currentState, context, fallbackTaskData), getDefaultInteractionUIControls(), 'main');
538
+ default:
539
+ return getDefaultUIControls();
540
+ }
541
+ }
542
+ function haveInteractionUIControlsChanged(previous, next) {
543
+ return Object.keys(next).some(key => {
544
+ const prev = previous[key];
545
+ const curr = next[key];
546
+ return prev.isVisible !== curr.isVisible || prev.isEnabled !== curr.isEnabled;
547
+ });
548
+ }
549
+ function haveUIControlsChanged(previous, next) {
550
+ if (!previous) return true;
551
+ return previous.activeLeg !== next.activeLeg || haveInteractionUIControlsChanged(previous.main, next.main) || haveInteractionUIControlsChanged(previous.consult, next.consult);
552
+ }
553
+ //# sourceMappingURL=uiControlsComputer.js.map