@webex/contact-center 3.12.0-next.9 → 3.12.0-task-refactor.2

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 (200) hide show
  1. package/AGENTS.md +438 -0
  2. package/ai-docs/README.md +131 -0
  3. package/ai-docs/RULES.md +455 -0
  4. package/ai-docs/patterns/event-driven-patterns.md +485 -0
  5. package/ai-docs/patterns/testing-patterns.md +480 -0
  6. package/ai-docs/patterns/typescript-patterns.md +365 -0
  7. package/ai-docs/templates/README.md +102 -0
  8. package/ai-docs/templates/documentation/create-agents-md.md +240 -0
  9. package/ai-docs/templates/documentation/create-architecture-md.md +295 -0
  10. package/ai-docs/templates/existing-service/bug-fix.md +254 -0
  11. package/ai-docs/templates/existing-service/feature-enhancement.md +450 -0
  12. package/ai-docs/templates/new-method/00-master.md +80 -0
  13. package/ai-docs/templates/new-method/01-requirements.md +232 -0
  14. package/ai-docs/templates/new-method/02-implementation.md +295 -0
  15. package/ai-docs/templates/new-method/03-tests.md +201 -0
  16. package/ai-docs/templates/new-method/04-validation.md +141 -0
  17. package/ai-docs/templates/new-service/00-master.md +109 -0
  18. package/ai-docs/templates/new-service/01-pre-questions.md +159 -0
  19. package/ai-docs/templates/new-service/02-code-generation.md +346 -0
  20. package/ai-docs/templates/new-service/03-integration.md +178 -0
  21. package/ai-docs/templates/new-service/04-test-generation.md +205 -0
  22. package/ai-docs/templates/new-service/05-validation.md +145 -0
  23. package/dist/cc.js +65 -123
  24. package/dist/cc.js.map +1 -1
  25. package/dist/constants.js +13 -2
  26. package/dist/constants.js.map +1 -1
  27. package/dist/index.js +13 -5
  28. package/dist/index.js.map +1 -1
  29. package/dist/metrics/behavioral-events.js +26 -13
  30. package/dist/metrics/behavioral-events.js.map +1 -1
  31. package/dist/metrics/constants.js +7 -6
  32. package/dist/metrics/constants.js.map +1 -1
  33. package/dist/services/ApiAiAssistant.js +0 -3
  34. package/dist/services/ApiAiAssistant.js.map +1 -1
  35. package/dist/services/config/Util.js +2 -3
  36. package/dist/services/config/Util.js.map +1 -1
  37. package/dist/services/config/types.js +16 -14
  38. package/dist/services/config/types.js.map +1 -1
  39. package/dist/services/constants.js +0 -1
  40. package/dist/services/constants.js.map +1 -1
  41. package/dist/services/core/Err.js.map +1 -1
  42. package/dist/services/core/Utils.js +79 -55
  43. package/dist/services/core/Utils.js.map +1 -1
  44. package/dist/services/core/aqm-reqs.js +17 -92
  45. package/dist/services/core/aqm-reqs.js.map +1 -1
  46. package/dist/services/core/websocket/WebSocketManager.js +5 -25
  47. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  48. package/dist/services/core/websocket/types.js.map +1 -1
  49. package/dist/services/index.js +1 -2
  50. package/dist/services/index.js.map +1 -1
  51. package/dist/services/task/Task.js +644 -0
  52. package/dist/services/task/Task.js.map +1 -0
  53. package/dist/services/task/TaskFactory.js +45 -0
  54. package/dist/services/task/TaskFactory.js.map +1 -0
  55. package/dist/services/task/TaskManager.js +570 -535
  56. package/dist/services/task/TaskManager.js.map +1 -1
  57. package/dist/services/task/TaskUtils.js +132 -28
  58. package/dist/services/task/TaskUtils.js.map +1 -1
  59. package/dist/services/task/constants.js +7 -6
  60. package/dist/services/task/constants.js.map +1 -1
  61. package/dist/services/task/dialer.js +0 -51
  62. package/dist/services/task/dialer.js.map +1 -1
  63. package/dist/services/task/digital/Digital.js +77 -0
  64. package/dist/services/task/digital/Digital.js.map +1 -0
  65. package/dist/services/task/state-machine/TaskStateMachine.js +634 -0
  66. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
  67. package/dist/services/task/state-machine/actions.js +372 -0
  68. package/dist/services/task/state-machine/actions.js.map +1 -0
  69. package/dist/services/task/state-machine/constants.js +139 -0
  70. package/dist/services/task/state-machine/constants.js.map +1 -0
  71. package/dist/services/task/state-machine/guards.js +263 -0
  72. package/dist/services/task/state-machine/guards.js.map +1 -0
  73. package/dist/services/task/state-machine/index.js +53 -0
  74. package/dist/services/task/state-machine/index.js.map +1 -0
  75. package/dist/services/task/state-machine/types.js +54 -0
  76. package/dist/services/task/state-machine/types.js.map +1 -0
  77. package/dist/services/task/state-machine/uiControlsComputer.js +377 -0
  78. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
  79. package/dist/services/task/taskDataNormalizer.js +99 -0
  80. package/dist/services/task/taskDataNormalizer.js.map +1 -0
  81. package/dist/services/task/types.js +157 -18
  82. package/dist/services/task/types.js.map +1 -1
  83. package/dist/services/task/voice/Voice.js +1031 -0
  84. package/dist/services/task/voice/Voice.js.map +1 -0
  85. package/dist/services/task/voice/WebRTC.js +149 -0
  86. package/dist/services/task/voice/WebRTC.js.map +1 -0
  87. package/dist/types/cc.d.ts +4 -33
  88. package/dist/types/constants.d.ts +13 -2
  89. package/dist/types/index.d.ts +11 -5
  90. package/dist/types/metrics/constants.d.ts +5 -3
  91. package/dist/types/services/ApiAiAssistant.d.ts +1 -1
  92. package/dist/types/services/config/types.d.ts +97 -25
  93. package/dist/types/services/core/Err.d.ts +0 -2
  94. package/dist/types/services/core/Utils.d.ts +25 -23
  95. package/dist/types/services/core/aqm-reqs.d.ts +0 -49
  96. package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -1
  97. package/dist/types/services/core/websocket/connection-service.d.ts +0 -1
  98. package/dist/types/services/core/websocket/types.d.ts +1 -1
  99. package/dist/types/services/index.d.ts +1 -1
  100. package/dist/types/services/task/Task.d.ts +146 -0
  101. package/dist/types/services/task/TaskFactory.d.ts +12 -0
  102. package/dist/types/services/task/TaskUtils.d.ts +39 -8
  103. package/dist/types/services/task/constants.d.ts +5 -4
  104. package/dist/types/services/task/dialer.d.ts +0 -15
  105. package/dist/types/services/task/digital/Digital.d.ts +22 -0
  106. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +906 -0
  107. package/dist/types/services/task/state-machine/actions.d.ts +8 -0
  108. package/dist/types/services/task/state-machine/constants.d.ts +91 -0
  109. package/dist/types/services/task/state-machine/guards.d.ts +78 -0
  110. package/dist/types/services/task/state-machine/index.d.ts +13 -0
  111. package/dist/types/services/task/state-machine/types.d.ts +256 -0
  112. package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
  113. package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
  114. package/dist/types/services/task/types.d.ts +539 -88
  115. package/dist/types/services/task/voice/Voice.d.ts +183 -0
  116. package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
  117. package/dist/types/types.d.ts +68 -0
  118. package/dist/types/webex.d.ts +1 -0
  119. package/dist/types.js +70 -0
  120. package/dist/types.js.map +1 -1
  121. package/dist/webex.js +14 -2
  122. package/dist/webex.js.map +1 -1
  123. package/package.json +14 -11
  124. package/src/cc.ts +91 -177
  125. package/src/constants.ts +13 -2
  126. package/src/index.ts +14 -5
  127. package/src/metrics/ai-docs/AGENTS.md +348 -0
  128. package/src/metrics/ai-docs/ARCHITECTURE.md +336 -0
  129. package/src/metrics/behavioral-events.ts +28 -14
  130. package/src/metrics/constants.ts +7 -8
  131. package/src/services/ApiAiAssistant.ts +2 -4
  132. package/src/services/agent/ai-docs/AGENTS.md +238 -0
  133. package/src/services/agent/ai-docs/ARCHITECTURE.md +302 -0
  134. package/src/services/ai-docs/AGENTS.md +384 -0
  135. package/src/services/config/Util.ts +2 -3
  136. package/src/services/config/ai-docs/AGENTS.md +253 -0
  137. package/src/services/config/ai-docs/ARCHITECTURE.md +424 -0
  138. package/src/services/config/types.ts +108 -20
  139. package/src/services/constants.ts +0 -1
  140. package/src/services/core/Err.ts +0 -1
  141. package/src/services/core/Utils.ts +90 -67
  142. package/src/services/core/ai-docs/AGENTS.md +379 -0
  143. package/src/services/core/ai-docs/ARCHITECTURE.md +696 -0
  144. package/src/services/core/aqm-reqs.ts +22 -100
  145. package/src/services/core/websocket/WebSocketManager.ts +4 -23
  146. package/src/services/core/websocket/types.ts +1 -1
  147. package/src/services/index.ts +1 -2
  148. package/src/services/task/Task.ts +785 -0
  149. package/src/services/task/TaskFactory.ts +55 -0
  150. package/src/services/task/TaskManager.ts +579 -633
  151. package/src/services/task/TaskUtils.ts +175 -31
  152. package/src/services/task/ai-docs/AGENTS.md +448 -0
  153. package/src/services/task/ai-docs/ARCHITECTURE.md +573 -0
  154. package/src/services/task/constants.ts +5 -4
  155. package/src/services/task/dialer.ts +1 -56
  156. package/src/services/task/digital/Digital.ts +95 -0
  157. package/src/services/task/state-machine/TaskStateMachine.ts +793 -0
  158. package/src/services/task/state-machine/actions.ts +422 -0
  159. package/src/services/task/state-machine/ai-docs/AGENTS.md +495 -0
  160. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1135 -0
  161. package/src/services/task/state-machine/constants.ts +150 -0
  162. package/src/services/task/state-machine/guards.ts +303 -0
  163. package/src/services/task/state-machine/index.ts +28 -0
  164. package/src/services/task/state-machine/types.ts +228 -0
  165. package/src/services/task/state-machine/uiControlsComputer.ts +542 -0
  166. package/src/services/task/taskDataNormalizer.ts +137 -0
  167. package/src/services/task/types.ts +641 -95
  168. package/src/services/task/voice/Voice.ts +1255 -0
  169. package/src/services/task/voice/WebRTC.ts +187 -0
  170. package/src/types.ts +88 -5
  171. package/src/utils/AGENTS.md +276 -0
  172. package/src/webex.js +2 -0
  173. package/test/unit/spec/cc.ts +59 -142
  174. package/test/unit/spec/logger-proxy.ts +70 -0
  175. package/test/unit/spec/services/ApiAiAssistant.ts +17 -0
  176. package/test/unit/spec/services/config/index.ts +26 -55
  177. package/test/unit/spec/services/core/Utils.ts +103 -52
  178. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +48 -112
  179. package/test/unit/spec/services/core/websocket/connection-service.ts +5 -4
  180. package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
  181. package/test/unit/spec/services/task/Task.ts +416 -0
  182. package/test/unit/spec/services/task/TaskFactory.ts +62 -0
  183. package/test/unit/spec/services/task/TaskManager.ts +781 -1735
  184. package/test/unit/spec/services/task/TaskUtils.ts +125 -0
  185. package/test/unit/spec/services/task/dialer.ts +112 -198
  186. package/test/unit/spec/services/task/digital/Digital.ts +105 -0
  187. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +473 -0
  188. package/test/unit/spec/services/task/state-machine/guards.ts +288 -0
  189. package/test/unit/spec/services/task/state-machine/types.ts +18 -0
  190. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +147 -0
  191. package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
  192. package/test/unit/spec/services/task/voice/Voice.ts +587 -0
  193. package/test/unit/spec/services/task/voice/WebRTC.ts +242 -0
  194. package/umd/contact-center.min.js +2 -2
  195. package/umd/contact-center.min.js.map +1 -1
  196. package/dist/services/task/index.js +0 -1525
  197. package/dist/services/task/index.js.map +0 -1
  198. package/dist/types/services/task/index.d.ts +0 -650
  199. package/src/services/task/index.ts +0 -1801
  200. package/test/unit/spec/services/task/index.ts +0 -2184
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Constants for the task state machine.
3
+ * These enums define the allowed states, events, and built-in action identifiers.
4
+ */
5
+
6
+ // ============================================
7
+ // Conference Constants
8
+ // ============================================
9
+
10
+ /**
11
+ * Maximum number of participants allowed in a multi-party conference.
12
+ * Max 7 counted agents + 1 customer.
13
+ */
14
+ export const MAX_PARTICIPANTS_IN_MULTIPARTY_CONFERENCE = 7;
15
+
16
+ // ============================================
17
+ // Participant Type Constants
18
+ // ============================================
19
+
20
+ /** Participant types for conference filtering */
21
+ export const PARTICIPANT_TYPE = {
22
+ CUSTOMER: 'Customer',
23
+ SUPERVISOR: 'Supervisor',
24
+ VVA: 'VVA',
25
+ } as const;
26
+
27
+ export type ParticipantType = (typeof PARTICIPANT_TYPE)[keyof typeof PARTICIPANT_TYPE];
28
+
29
+ // ============================================
30
+ // Media Type Constants
31
+ // ============================================
32
+
33
+ /** Media type for consult calls */
34
+ export const MEDIA_TYPE_CONSULT = 'consult';
35
+
36
+ /** Media type for main calls */
37
+ export const MEDIA_TYPE_MAIN_CALL = 'mainCall';
38
+
39
+ // ============================================
40
+ // State Machine Enums
41
+ // ============================================
42
+
43
+ export enum TaskState {
44
+ IDLE = 'IDLE',
45
+ OFFERED = 'OFFERED',
46
+ CONNECTED = 'CONNECTED',
47
+
48
+ // Intermediate states for async operations
49
+ HOLD_INITIATING = 'HOLD_INITIATING',
50
+ HELD = 'HELD',
51
+ RESUME_INITIATING = 'RESUME_INITIATING',
52
+ CONSULT_INITIATING = 'CONSULT_INITIATING',
53
+ CONSULTING = 'CONSULTING',
54
+ CONF_INITIATING = 'CONF_INITIATING',
55
+
56
+ CONFERENCING = 'CONFERENCING',
57
+ WRAPPING_UP = 'WRAPPING_UP',
58
+ COMPLETED = 'COMPLETED',
59
+ TERMINATED = 'TERMINATED',
60
+
61
+ // NOT IMPLEMENTED: MPC (Multi-Party Conference) states
62
+ CONSULT_INITIATED = 'CONSULT_INITIATED',
63
+ CONSULT_COMPLETED = 'CONSULT_COMPLETED',
64
+ // NOT IMPLEMENTED: Post-call state (isWxccPostCallEnabled feature flag)
65
+ POST_CALL = 'POST_CALL',
66
+ // NOT IMPLEMENTED: Parked state
67
+ PARKED = 'PARKED',
68
+ // NOT IMPLEMENTED: Monitoring/Supervisory states
69
+ MONITORING = 'MONITORING',
70
+ }
71
+
72
+ export enum TaskEvent {
73
+ TASK_INCOMING = 'TASK_INCOMING',
74
+ TASK_OFFERED = 'TASK_OFFERED',
75
+
76
+ // Offer events
77
+ OFFER_CONSULT = 'OFFER_CONSULT',
78
+ HYDRATE = 'HYDRATE',
79
+
80
+ // Internal "data refresh" events
81
+ CONTACT_UPDATED = 'CONTACT_UPDATED',
82
+ CONTACT_OWNER_CHANGED = 'CONTACT_OWNER_CHANGED',
83
+
84
+ // Assignment events
85
+ ASSIGN = 'ASSIGN',
86
+
87
+ // Hold/Resume events
88
+ HOLD_SUCCESS = 'HOLD_SUCCESS',
89
+ HOLD_FAILED = 'HOLD_FAILED',
90
+ UNHOLD_SUCCESS = 'UNHOLD_SUCCESS',
91
+ UNHOLD_FAILED = 'UNHOLD_FAILED',
92
+ HOLD_INITIATED = 'HOLD_INITIATED',
93
+ UNHOLD_INITIATED = 'UNHOLD_INITIATED',
94
+
95
+ // Consult events
96
+ CONSULT = 'CONSULT',
97
+ CONSULT_SUCCESS = 'CONSULT_SUCCESS',
98
+ CONSULT_CREATED = 'CONSULT_CREATED',
99
+ CONSULTING_ACTIVE = 'CONSULTING_ACTIVE',
100
+ CONSULT_END = 'CONSULT_END',
101
+ CONSULT_FAILED = 'CONSULT_FAILED',
102
+
103
+ // Conference events
104
+ MERGE_TO_CONFERENCE = 'MERGE_TO_CONFERENCE',
105
+ CONFERENCE_START = 'CONFERENCE_START',
106
+ CONFERENCE_FAILED = 'CONFERENCE_FAILED',
107
+ CONFERENCE_END = 'CONFERENCE_END',
108
+ TRANSFER_CONFERENCE = 'TRANSFER_CONFERENCE',
109
+ TRANSFER_CONFERENCE_SUCCESS = 'TRANSFER_CONFERENCE_SUCCESS',
110
+ TRANSFER_CONFERENCE_FAILED = 'TRANSFER_CONFERENCE_FAILED',
111
+ PARTICIPANT_LEAVE = 'PARTICIPANT_LEAVE',
112
+ EXIT_CONFERENCE = 'EXIT_CONFERENCE',
113
+ EXIT_CONFERENCE_SUCCESS = 'EXIT_CONFERENCE_SUCCESS',
114
+ EXIT_CONFERENCE_FAILED = 'EXIT_CONFERENCE_FAILED',
115
+
116
+ // Recording events
117
+ RECORDING_STARTED = 'RECORDING_STARTED',
118
+ PAUSE_RECORDING = 'PAUSE_RECORDING',
119
+ RESUME_RECORDING = 'RESUME_RECORDING',
120
+
121
+ // Transfer events
122
+ TRANSFER_SUCCESS = 'TRANSFER_SUCCESS',
123
+ TRANSFER_FAILED = 'TRANSFER_FAILED',
124
+
125
+ // Wrapup events
126
+ WRAPUP_COMPLETE = 'WRAPUP_COMPLETE',
127
+
128
+ // End events
129
+ TASK_WRAPUP = 'TASK_WRAPUP',
130
+ RONA = 'RONA', // Ring On No Answer
131
+ CONTACT_ENDED = 'CONTACT_ENDED',
132
+
133
+ // Failure events
134
+ ASSIGN_FAILED = 'ASSIGN_FAILED',
135
+ INVITE_FAILED = 'INVITE_FAILED',
136
+ OUTBOUND_FAILED = 'OUTBOUND_FAILED',
137
+
138
+ // Switch events (toggle between consult and main call)
139
+ SWITCH_TO_MAIN_CALL = 'SWITCH_TO_MAIN_CALL',
140
+ SWITCH_TO_CONSULT = 'SWITCH_TO_CONSULT',
141
+
142
+ // Accept/Decline (WebRTC)
143
+ ACCEPT = 'ACCEPT',
144
+ DECLINE = 'DECLINE',
145
+ END = 'END',
146
+
147
+ // Queue events
148
+ CTQ_CANCEL = 'CTQ_CANCEL', // Cancel To Queue
149
+ CTQ_CANCEL_FAILED = 'CTQ_CANCEL_FAILED',
150
+ }
@@ -0,0 +1,303 @@
1
+ /**
2
+ * Task State Machine Guards
3
+ *
4
+ * Guard functions that determine if a state transition is allowed.
5
+ * These functions validate the current context before allowing transitions.
6
+ *
7
+ * All guards are consolidated here for:
8
+ * - Single source of truth
9
+ * - Easy testing
10
+ * - Reusability across state machine transitions
11
+ *
12
+ * Guards are organized by category:
13
+ * 1. Helper Functions - Extract data from events/context
14
+ * 2. Hydrate Guards - For state restoration on page refresh
15
+ * 3. Conference Guards - Conference state checks
16
+ * 4. Customer Guards - Customer presence checks
17
+ * 5. Consult Guards - Consult flow checks
18
+ * 6. Wrapup Guards - End-of-call flow checks
19
+ * 7. Server State Guards - Check backend-reported state
20
+ * 8. Recording Guards - Recording state checks
21
+ */
22
+
23
+ import {TaskContext, TaskEventPayload} from './types';
24
+ import {TaskData} from '../types';
25
+ import {
26
+ getIsCustomerInCall,
27
+ getConferenceParticipantsCount,
28
+ getIsConferenceInProgress,
29
+ } from '../TaskUtils';
30
+ import {TaskEvent} from './constants';
31
+
32
+ export const getTaskDataFromEvent = (event?: TaskEventPayload): TaskData | undefined =>
33
+ event && typeof event === 'object' && 'taskData' in event
34
+ ? (event as {taskData?: TaskData}).taskData
35
+ : undefined;
36
+
37
+ export const getSelfAgentId = (context: TaskContext, taskData?: TaskData): string | undefined =>
38
+ context.uiControlConfig?.agentId ?? context.taskData?.agentId ?? taskData?.agentId;
39
+
40
+ export const isSelfConsultingAgent = (context: TaskContext, taskData?: TaskData): boolean => {
41
+ const selfAgentId = getSelfAgentId(context, taskData);
42
+ if (!selfAgentId) return false;
43
+
44
+ return taskData?.consultingAgentId === selfAgentId;
45
+ };
46
+
47
+ /**
48
+ * Determines if this agent should enter WRAPPING_UP state.
49
+ * Priority: agentsPendingWrapUp > wrapUpRequired / participant.isWrapUp > ownership > !isConsulted
50
+ */
51
+ export const shouldWrapUpForThisAgent = (context: TaskContext, taskData: TaskData): boolean => {
52
+ const selfAgentId = getSelfAgentId(context, taskData);
53
+ if (!selfAgentId) return false;
54
+
55
+ const pending = taskData?.agentsPendingWrapUp;
56
+ if (Array.isArray(pending) && pending.length > 0) {
57
+ return pending.includes(selfAgentId);
58
+ }
59
+
60
+ const participantWrapUp = taskData?.interaction?.participants?.[selfAgentId]?.isWrapUp === true;
61
+ const wrapUpRequired = taskData?.wrapUpRequired === true;
62
+ if (wrapUpRequired || participantWrapUp) {
63
+ return true;
64
+ }
65
+
66
+ const owner = taskData?.interaction?.owner;
67
+ if (owner && owner === selfAgentId) {
68
+ return true;
69
+ }
70
+
71
+ if (taskData?.isConsulted === false) {
72
+ return true;
73
+ }
74
+
75
+ return false;
76
+ };
77
+
78
+ export interface GuardParams {
79
+ context: TaskContext;
80
+ event?: TaskEventPayload;
81
+ }
82
+
83
+ export type GuardFunction = (params: GuardParams) => boolean;
84
+
85
+ export const guards = {
86
+ // Hydrate Guards
87
+ isInteractionTerminated: ({context, event}: GuardParams): boolean => {
88
+ const taskData = getTaskDataFromEvent(event);
89
+
90
+ if (taskData?.interaction?.isTerminated === true) return true;
91
+
92
+ const selfAgentId = getSelfAgentId(context, taskData);
93
+ if (selfAgentId && taskData?.interaction?.participants?.[selfAgentId]?.isWrapUp === true) {
94
+ return true;
95
+ }
96
+
97
+ return false;
98
+ },
99
+
100
+ isInteractionConsulting: ({event}: GuardParams): boolean => {
101
+ const taskData = getTaskDataFromEvent(event);
102
+
103
+ if (taskData?.interaction?.state === 'consulting') return true;
104
+
105
+ // EP_DN consulted agent: backend reports state as 'connected' but CPD indicates consult
106
+ const cpd = taskData?.interaction?.callProcessingDetails;
107
+ if (cpd?.relationshipType === 'consult' && taskData?.interaction?.state === 'connected') {
108
+ return true;
109
+ }
110
+
111
+ return false;
112
+ },
113
+
114
+ isInteractionHeld: ({event}: GuardParams): boolean => {
115
+ const taskData = getTaskDataFromEvent(event);
116
+
117
+ if (taskData?.interaction?.state === 'hold') return true;
118
+
119
+ const mainMediaId = taskData?.interaction?.mainInteractionId || taskData?.interactionId;
120
+ if (mainMediaId && taskData?.interaction?.media?.[mainMediaId]?.isHold === true) {
121
+ return true;
122
+ }
123
+
124
+ return false;
125
+ },
126
+
127
+ isInteractionConnected: ({event}: GuardParams): boolean => {
128
+ const taskData = getTaskDataFromEvent(event);
129
+
130
+ return taskData?.interaction?.state === 'connected';
131
+ },
132
+
133
+ isConferencingByParticipants: ({event}: GuardParams): boolean => {
134
+ const taskData = getTaskDataFromEvent(event);
135
+ if (!taskData) return false;
136
+ const mainCallId = taskData.interaction?.mainInteractionId || taskData.interactionId;
137
+ const media = taskData.interaction?.media?.[mainCallId];
138
+ const participants = taskData.interaction?.participants;
139
+ if (!media?.participants || !participants) return false;
140
+ let agentCount = 0;
141
+ for (const pId of media.participants) {
142
+ const p = participants[pId];
143
+ if (p && p.pType !== 'Customer' && p.pType !== 'Supervisor' && !p.hasLeft) {
144
+ agentCount += 1;
145
+ }
146
+ }
147
+
148
+ return agentCount >= 2;
149
+ },
150
+
151
+ // Conference Guards
152
+ conferenceInProgressFromEvent: ({event}: GuardParams): boolean => {
153
+ const taskData = getTaskDataFromEvent(event);
154
+ if (!taskData?.interaction) return false;
155
+
156
+ return getIsConferenceInProgress(taskData);
157
+ },
158
+
159
+ /**
160
+ * Conference downgrade check specifically for transitioning back to CONNECTED.
161
+ *
162
+ * Returns true only when:
163
+ * - conference has downgraded (fewer than 2 active agent participants in main call)
164
+ * - customer is still in the call
165
+ * - current agent is still in the main call
166
+ */
167
+ shouldDowngradeConferenceToConnected: ({context, event}: GuardParams): boolean => {
168
+ const eventTaskData = getTaskDataFromEvent(event);
169
+ const taskData = eventTaskData ?? context.taskData;
170
+ if (!taskData?.interaction) return false;
171
+
172
+ const selfAgentId = getSelfAgentId(context, taskData);
173
+ if (!selfAgentId) return false;
174
+
175
+ const mainCallId = taskData.interaction.mainInteractionId || taskData.interactionId;
176
+ if (!mainCallId) return false;
177
+
178
+ // Don't downgrade while backend still reports conference.
179
+ if (taskData.interaction.state === 'conference') return false;
180
+
181
+ const agentParticipantsCount = getConferenceParticipantsCount(taskData.interaction, mainCallId);
182
+ if (agentParticipantsCount >= 2) return false;
183
+
184
+ const customerInCall = getIsCustomerInCall(taskData.interaction, mainCallId);
185
+ if (!customerInCall) return false;
186
+
187
+ const selfInMainCall = Boolean(
188
+ taskData.interaction.media?.[mainCallId]?.participants?.includes(selfAgentId)
189
+ );
190
+
191
+ return selfInMainCall;
192
+ },
193
+
194
+ // Consult Guards
195
+ /**
196
+ * Check if this agent initiated the consult (using event data)
197
+ * Handles both consultingAgentId and fallback to context flag
198
+ */
199
+ didInitiateConsult: ({context, event}: GuardParams): boolean => {
200
+ const taskData = getTaskDataFromEvent(event);
201
+ if (taskData?.isConsulted === true) return false;
202
+
203
+ return taskData?.consultingAgentId
204
+ ? isSelfConsultingAgent(context, taskData)
205
+ : context.consultInitiator === true;
206
+ },
207
+
208
+ /**
209
+ * EP-DN / consulted consult legs can arrive as AGENT_CONTACT_ASSIGNED without a preceding
210
+ * AgentConsulting event. When that happens, we should enter CONSULTING (not CONNECTED).
211
+ */
212
+ isConsultingAssignment: ({event}: GuardParams): boolean => {
213
+ const taskData = getTaskDataFromEvent(event);
214
+ if (!taskData) return false;
215
+
216
+ const relationshipType = taskData.interaction?.callProcessingDetails?.relationshipType;
217
+
218
+ return (
219
+ taskData.isConsulted === true ||
220
+ relationshipType === 'consult' ||
221
+ taskData.interaction?.state === 'consulting'
222
+ );
223
+ },
224
+
225
+ // Wrapup Guards
226
+ shouldWrapUp: ({context, event}: GuardParams): boolean => {
227
+ const taskData = getTaskDataFromEvent(event);
228
+ if (!taskData) return false;
229
+
230
+ if (event?.type === TaskEvent.CONFERENCE_END) {
231
+ const selfAgentId = getSelfAgentId(context, taskData);
232
+ if (!selfAgentId) return false;
233
+
234
+ const pending = taskData?.agentsPendingWrapUp;
235
+ if (Array.isArray(pending) && pending.length > 0) {
236
+ return pending.includes(selfAgentId);
237
+ }
238
+
239
+ const participantWrapUp =
240
+ taskData?.interaction?.participants?.[selfAgentId]?.isWrapUp === true;
241
+ const wrapUpRequired = taskData?.wrapUpRequired === true;
242
+
243
+ return wrapUpRequired || participantWrapUp;
244
+ }
245
+
246
+ return shouldWrapUpForThisAgent(context, taskData);
247
+ },
248
+
249
+ /**
250
+ * Check if wrapUpRequired in payload OR is consult initiator
251
+ */
252
+ shouldWrapUpOrIsInitiator: ({context, event}: GuardParams): boolean => {
253
+ const taskData = getTaskDataFromEvent(event);
254
+
255
+ return Boolean(taskData?.wrapUpRequired || context.consultInitiator);
256
+ },
257
+
258
+ /**
259
+ * True if PARTICIPANT_LEAVE indicates that *this* agent left the conference.
260
+ *
261
+ * Important: PARTICIPANT_LEAVE is broadcast to all agents in the conference.
262
+ * Only the agent whose id matches the leaving participant should transition to
263
+ * TERMINATED / WRAPPING_UP based on wrapup rules.
264
+ */
265
+ didCurrentAgentLeaveConference: ({context, event}: GuardParams): boolean => {
266
+ const taskData = getTaskDataFromEvent(event);
267
+ const selfAgentId = getSelfAgentId(context, taskData);
268
+ if (!selfAgentId) return false;
269
+
270
+ const participantIdFromEvent =
271
+ event && typeof event === 'object' && 'participantId' in event
272
+ ? (event as {participantId?: string}).participantId
273
+ : undefined;
274
+ const participantId = participantIdFromEvent ?? taskData?.participantId;
275
+
276
+ if (Boolean(participantId) && participantId === selfAgentId) {
277
+ return true;
278
+ }
279
+
280
+ // For EP-DN agents the backend removes the leaving participant entirely
281
+ // from the participants map (rather than setting hasLeft). If this task
282
+ // is in CONFERENCING (implied by the guard being evaluated here) but the
283
+ // agent is absent from the updated participants, they have left.
284
+ const participants = taskData?.interaction?.participants;
285
+ if (participants && !(selfAgentId in participants)) {
286
+ return true;
287
+ }
288
+
289
+ return false;
290
+ },
291
+
292
+ // Server State Guards
293
+ isPrimaryMediaOnHold: ({event}: GuardParams): boolean => {
294
+ const taskData = getTaskDataFromEvent(event);
295
+ if (!taskData) return false;
296
+ const mediaId = taskData.mediaResourceId;
297
+ if (!mediaId) return false;
298
+
299
+ return taskData.interaction?.media?.[mediaId]?.isHold === true;
300
+ },
301
+ };
302
+
303
+ export type GuardName = keyof typeof guards;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Task State Machine
3
+ *
4
+ * Export all state machine components for easy importing
5
+ */
6
+
7
+ // Main state machine
8
+ export {createTaskStateMachine} from './TaskStateMachine';
9
+ export type {TaskStateMachine} from './TaskStateMachine';
10
+
11
+ // Types & enums
12
+ export {TaskState, TaskEvent} from './constants';
13
+ export {isEventOfType} from './types';
14
+ export type {
15
+ TaskContext,
16
+ TaskEventPayload,
17
+ TaskStateMachineConfig,
18
+ UIControlConfig,
19
+ TaskActionsMap,
20
+ TaskActionArgs,
21
+ } from './types';
22
+
23
+ // Guards
24
+ export {guards} from './guards';
25
+ export type {GuardParams, GuardFunction} from './guards';
26
+
27
+ // Actions
28
+ export {actions, createInitialContext} from './actions';