@webex/contact-center 3.11.0 → 3.12.0-llmrefactor.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 (267) hide show
  1. package/.sdd/manifest.json +882 -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 +379 -50
  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 +22 -2
  44. package/dist/constants.js.map +1 -1
  45. package/dist/index.js +27 -5
  46. package/dist/index.js.map +1 -1
  47. package/dist/metrics/behavioral-events.js +114 -0
  48. package/dist/metrics/behavioral-events.js.map +1 -1
  49. package/dist/metrics/constants.js +32 -3
  50. package/dist/metrics/constants.js.map +1 -1
  51. package/dist/services/ApiAiAssistant.js +318 -0
  52. package/dist/services/ApiAiAssistant.js.map +1 -0
  53. package/dist/services/UserPreference.js +427 -0
  54. package/dist/services/UserPreference.js.map +1 -0
  55. package/dist/services/agent/types.js.map +1 -1
  56. package/dist/services/config/Util.js +8 -4
  57. package/dist/services/config/Util.js.map +1 -1
  58. package/dist/services/config/constants.js +35 -2
  59. package/dist/services/config/constants.js.map +1 -1
  60. package/dist/services/config/index.js +41 -2
  61. package/dist/services/config/index.js.map +1 -1
  62. package/dist/services/config/types.js +66 -8
  63. package/dist/services/config/types.js.map +1 -1
  64. package/dist/services/constants.js +27 -1
  65. package/dist/services/constants.js.map +1 -1
  66. package/dist/services/core/Err.js.map +1 -1
  67. package/dist/services/core/Utils.js +122 -25
  68. package/dist/services/core/Utils.js.map +1 -1
  69. package/dist/services/core/aqm-reqs.js +92 -17
  70. package/dist/services/core/aqm-reqs.js.map +1 -1
  71. package/dist/services/core/websocket/WebSocketManager.js +22 -6
  72. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  73. package/dist/services/core/websocket/connection-service.js +3 -1
  74. package/dist/services/core/websocket/connection-service.js.map +1 -1
  75. package/dist/services/core/websocket/types.js.map +1 -1
  76. package/dist/services/index.js +6 -0
  77. package/dist/services/index.js.map +1 -1
  78. package/dist/services/task/Task.js +688 -0
  79. package/dist/services/task/Task.js.map +1 -0
  80. package/dist/services/task/TaskFactory.js +45 -0
  81. package/dist/services/task/TaskFactory.js.map +1 -0
  82. package/dist/services/task/TaskManager.js +751 -457
  83. package/dist/services/task/TaskManager.js.map +1 -1
  84. package/dist/services/task/TaskUtils.js +220 -23
  85. package/dist/services/task/TaskUtils.js.map +1 -1
  86. package/dist/services/task/constants.js +23 -2
  87. package/dist/services/task/constants.js.map +1 -1
  88. package/dist/services/task/dialer.js +129 -0
  89. package/dist/services/task/dialer.js.map +1 -1
  90. package/dist/services/task/digital/Digital.js +77 -0
  91. package/dist/services/task/digital/Digital.js.map +1 -0
  92. package/dist/services/task/state-machine/TaskStateMachine.js +873 -0
  93. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
  94. package/dist/services/task/state-machine/actions.js +567 -0
  95. package/dist/services/task/state-machine/actions.js.map +1 -0
  96. package/dist/services/task/state-machine/constants.js +161 -0
  97. package/dist/services/task/state-machine/constants.js.map +1 -0
  98. package/dist/services/task/state-machine/guards.js +382 -0
  99. package/dist/services/task/state-machine/guards.js.map +1 -0
  100. package/dist/services/task/state-machine/index.js +53 -0
  101. package/dist/services/task/state-machine/index.js.map +1 -0
  102. package/dist/services/task/state-machine/types.js +54 -0
  103. package/dist/services/task/state-machine/types.js.map +1 -0
  104. package/dist/services/task/state-machine/uiControlsComputer.js +603 -0
  105. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
  106. package/dist/services/task/taskDataNormalizer.js +99 -0
  107. package/dist/services/task/taskDataNormalizer.js.map +1 -0
  108. package/dist/services/task/types.js +227 -4
  109. package/dist/services/task/types.js.map +1 -1
  110. package/dist/services/task/voice/Voice.js +1044 -0
  111. package/dist/services/task/voice/Voice.js.map +1 -0
  112. package/dist/services/task/voice/WebRTC.js +149 -0
  113. package/dist/services/task/voice/WebRTC.js.map +1 -0
  114. package/dist/types/cc.d.ts +894 -0
  115. package/dist/types/config.d.ts +72 -0
  116. package/dist/types/constants.d.ts +66 -0
  117. package/dist/types/index.d.ts +199 -0
  118. package/dist/types/logger-proxy.d.ts +71 -0
  119. package/dist/types/metrics/MetricsManager.d.ts +223 -0
  120. package/dist/types/metrics/behavioral-events.d.ts +29 -0
  121. package/dist/types/metrics/constants.d.ts +181 -0
  122. package/dist/types/services/AddressBook.d.ts +74 -0
  123. package/dist/types/services/ApiAiAssistant.d.ts +49 -0
  124. package/dist/types/services/EntryPoint.d.ts +67 -0
  125. package/dist/types/services/Queue.d.ts +76 -0
  126. package/dist/types/services/UserPreference.d.ts +118 -0
  127. package/dist/types/services/WebCallingService.d.ts +1 -0
  128. package/dist/types/services/agent/index.d.ts +46 -0
  129. package/dist/types/services/agent/types.d.ts +413 -0
  130. package/dist/types/services/config/Util.d.ts +20 -0
  131. package/dist/types/services/config/constants.d.ts +270 -0
  132. package/dist/types/services/config/index.d.ts +177 -0
  133. package/dist/types/services/config/types.d.ts +1368 -0
  134. package/dist/types/services/constants.d.ts +110 -0
  135. package/dist/types/services/core/Err.d.ts +125 -0
  136. package/dist/types/services/core/GlobalTypes.d.ts +58 -0
  137. package/dist/types/services/core/Utils.d.ts +121 -0
  138. package/dist/types/services/core/WebexRequest.d.ts +22 -0
  139. package/dist/types/services/core/aqm-reqs.d.ts +65 -0
  140. package/dist/types/services/core/constants.d.ts +99 -0
  141. package/dist/types/services/core/types.d.ts +47 -0
  142. package/dist/types/services/core/websocket/WebSocketManager.d.ts +36 -0
  143. package/dist/types/services/core/websocket/connection-service.d.ts +27 -0
  144. package/dist/types/services/core/websocket/keepalive.worker.d.ts +2 -0
  145. package/dist/types/services/core/websocket/types.d.ts +37 -0
  146. package/dist/types/services/index.d.ts +54 -0
  147. package/dist/types/services/task/AutoWrapup.d.ts +40 -0
  148. package/dist/types/services/task/Task.d.ts +157 -0
  149. package/dist/types/services/task/TaskFactory.d.ts +12 -0
  150. package/dist/types/services/task/TaskManager.d.ts +1 -0
  151. package/dist/types/services/task/TaskUtils.d.ts +138 -0
  152. package/dist/types/services/task/constants.d.ts +91 -0
  153. package/dist/types/services/task/contact.d.ts +69 -0
  154. package/dist/types/services/task/dialer.d.ts +73 -0
  155. package/dist/types/services/task/digital/Digital.d.ts +22 -0
  156. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +1194 -0
  157. package/dist/types/services/task/state-machine/actions.d.ts +10 -0
  158. package/dist/types/services/task/state-machine/constants.d.ts +107 -0
  159. package/dist/types/services/task/state-machine/guards.d.ts +102 -0
  160. package/dist/types/services/task/state-machine/index.d.ts +13 -0
  161. package/dist/types/services/task/state-machine/types.d.ts +269 -0
  162. package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
  163. package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
  164. package/dist/types/services/task/types.d.ts +1856 -0
  165. package/dist/types/services/task/voice/Voice.d.ts +184 -0
  166. package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
  167. package/dist/types/types.d.ts +778 -0
  168. package/dist/types/utils/PageCache.d.ts +173 -0
  169. package/dist/types/webex-config.d.ts +53 -0
  170. package/dist/types/webex.d.ts +8 -0
  171. package/dist/types.js +130 -1
  172. package/dist/types.js.map +1 -1
  173. package/dist/webex.js +14 -2
  174. package/dist/webex.js.map +1 -1
  175. package/package.json +16 -12
  176. package/src/cc.ts +477 -51
  177. package/src/config.ts +6 -0
  178. package/src/constants.ts +21 -1
  179. package/src/index.ts +24 -5
  180. package/src/metrics/ai-docs/AGENTS.md +350 -0
  181. package/src/metrics/ai-docs/ARCHITECTURE.md +338 -0
  182. package/src/metrics/ai-docs/metrics-spec.md +854 -0
  183. package/src/metrics/behavioral-events.ts +120 -0
  184. package/src/metrics/constants.ts +37 -3
  185. package/src/services/ApiAiAssistant.ts +412 -0
  186. package/src/services/UserPreference.ts +509 -0
  187. package/src/services/agent/ai-docs/AGENTS.md +240 -0
  188. package/src/services/agent/ai-docs/ARCHITECTURE.md +304 -0
  189. package/src/services/agent/ai-docs/agent-spec.md +504 -0
  190. package/src/services/agent/types.ts +1 -1
  191. package/src/services/ai-docs/AGENTS.md +386 -0
  192. package/src/services/ai-docs/services-spec.md +492 -0
  193. package/src/services/config/Util.ts +10 -2
  194. package/src/services/config/ai-docs/AGENTS.md +255 -0
  195. package/src/services/config/ai-docs/ARCHITECTURE.md +426 -0
  196. package/src/services/config/ai-docs/config-spec.md +669 -0
  197. package/src/services/config/constants.ts +37 -1
  198. package/src/services/config/index.ts +45 -1
  199. package/src/services/config/types.ts +241 -11
  200. package/src/services/constants.ts +29 -0
  201. package/src/services/core/Err.ts +3 -0
  202. package/src/services/core/Utils.ts +143 -30
  203. package/src/services/core/ai-docs/AGENTS.md +381 -0
  204. package/src/services/core/ai-docs/ARCHITECTURE.md +698 -0
  205. package/src/services/core/ai-docs/core-spec.md +783 -0
  206. package/src/services/core/aqm-reqs.ts +100 -22
  207. package/src/services/core/websocket/WebSocketManager.ts +23 -6
  208. package/src/services/core/websocket/connection-service.ts +5 -1
  209. package/src/services/core/websocket/types.ts +1 -1
  210. package/src/services/index.ts +4 -0
  211. package/src/services/task/Task.ts +837 -0
  212. package/src/services/task/TaskFactory.ts +55 -0
  213. package/src/services/task/TaskManager.ts +793 -521
  214. package/src/services/task/TaskUtils.ts +314 -24
  215. package/src/services/task/ai-docs/AGENTS.md +457 -0
  216. package/src/services/task/ai-docs/ARCHITECTURE.md +594 -0
  217. package/src/services/task/ai-docs/task-spec.md +1319 -0
  218. package/src/services/task/constants.ts +23 -0
  219. package/src/services/task/dialer.ts +136 -1
  220. package/src/services/task/digital/Digital.ts +95 -0
  221. package/src/services/task/state-machine/TaskStateMachine.ts +1166 -0
  222. package/src/services/task/state-machine/actions.ts +738 -0
  223. package/src/services/task/state-machine/ai-docs/AGENTS.md +458 -0
  224. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1137 -0
  225. package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +2177 -0
  226. package/src/services/task/state-machine/constants.ts +172 -0
  227. package/src/services/task/state-machine/guards.ts +445 -0
  228. package/src/services/task/state-machine/index.ts +28 -0
  229. package/src/services/task/state-machine/types.ts +243 -0
  230. package/src/services/task/state-machine/uiControlsComputer.ts +961 -0
  231. package/src/services/task/taskDataNormalizer.ts +137 -0
  232. package/src/services/task/types.ts +734 -71
  233. package/src/services/task/voice/Voice.ts +1270 -0
  234. package/src/services/task/voice/WebRTC.ts +187 -0
  235. package/src/types.ts +205 -2
  236. package/src/utils/AGENTS.md +278 -0
  237. package/src/utils/ai-docs/utils-spec.md +381 -0
  238. package/src/webex.js +2 -0
  239. package/test/unit/spec/cc.ts +503 -43
  240. package/test/unit/spec/logger-proxy.ts +70 -0
  241. package/test/unit/spec/services/ApiAiAssistant.ts +273 -0
  242. package/test/unit/spec/services/UserPreference.ts +401 -0
  243. package/test/unit/spec/services/WebCallingService.ts +7 -1
  244. package/test/unit/spec/services/config/index.ts +85 -29
  245. package/test/unit/spec/services/core/Utils.ts +481 -2
  246. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +137 -41
  247. package/test/unit/spec/services/core/websocket/connection-service.ts +3 -1
  248. package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
  249. package/test/unit/spec/services/task/Task.ts +477 -0
  250. package/test/unit/spec/services/task/TaskFactory.ts +62 -0
  251. package/test/unit/spec/services/task/TaskManager.ts +1001 -1003
  252. package/test/unit/spec/services/task/TaskUtils.ts +235 -0
  253. package/test/unit/spec/services/task/dialer.ts +372 -96
  254. package/test/unit/spec/services/task/digital/Digital.ts +105 -0
  255. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +2651 -0
  256. package/test/unit/spec/services/task/state-machine/guards.ts +637 -0
  257. package/test/unit/spec/services/task/state-machine/types.ts +18 -0
  258. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +2663 -0
  259. package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
  260. package/test/unit/spec/services/task/voice/Voice.ts +649 -0
  261. package/test/unit/spec/services/task/voice/WebRTC.ts +235 -0
  262. package/umd/contact-center.min.js +2 -2
  263. package/umd/contact-center.min.js.map +1 -1
  264. package/dist/services/task/index.js +0 -1525
  265. package/dist/services/task/index.js.map +0 -1
  266. package/src/services/task/index.ts +0 -1801
  267. package/test/unit/spec/services/task/index.ts +0 -2184
@@ -0,0 +1,637 @@
1
+ import {guards, GuardParams} from '../../../../../../src/services/task/state-machine/guards';
2
+ import {
3
+ TaskContext,
4
+ TaskEventPayload,
5
+ } from '../../../../../../src/services/task/state-machine/types';
6
+ import {TaskData, InteractionParticipant} from '../../../../../../src/services/task/types';
7
+
8
+ describe('State Machine Guards', () => {
9
+ const createParticipant = (
10
+ id: string,
11
+ pType: string,
12
+ hasLeft = false
13
+ ): InteractionParticipant => ({
14
+ id,
15
+ pType,
16
+ type: pType,
17
+ hasJoined: true,
18
+ hasLeft,
19
+ isInPredial: false,
20
+ });
21
+ // Note: media key must match mainInteractionId for getIsConferenceInProgress to work
22
+ const INTERACTION_ID = 'interaction-123';
23
+
24
+ const createTaskData = (overrides: Partial<TaskData> = {}): TaskData =>
25
+ ({
26
+ mediaResourceId: INTERACTION_ID,
27
+ eventType: 'TEST_EVENT',
28
+ agentId: 'agent-123',
29
+ destAgentId: '',
30
+ trackingId: 'track-123',
31
+ consultMediaResourceId: '',
32
+ interactionId: INTERACTION_ID,
33
+ orgId: 'org-123',
34
+ owner: 'agent-123',
35
+ queueMgr: 'queue-mgr',
36
+ type: 'voice',
37
+ isConferencing: false,
38
+ interaction: {
39
+ mainInteractionId: INTERACTION_ID,
40
+ interactionId: INTERACTION_ID,
41
+ participants: {},
42
+ media: {
43
+ [INTERACTION_ID]: {
44
+ mediaResourceId: INTERACTION_ID,
45
+ mediaType: 'telephony',
46
+ mediaMgr: 'media-mgr',
47
+ participants: ['agent-123'],
48
+ mType: 'mainCall',
49
+ isHold: false,
50
+ holdTimestamp: null,
51
+ },
52
+ },
53
+ owner: 'agent-123',
54
+ mediaChannel: 'telephony',
55
+ contactDirection: {type: 'inbound'},
56
+ },
57
+ ...overrides,
58
+ } as TaskData);
59
+
60
+ const createContext = (overrides: Partial<TaskContext> = {}): TaskContext =>
61
+ ({
62
+ recordingControlsAvailable: false,
63
+ recordingInProgress: false,
64
+ consultDestinationAgentJoined: false,
65
+ consultCallHeld: false,
66
+ consultInitiator: false,
67
+ exitingConference: false,
68
+ hideBlindTransferForEpDnPendingMerge: false,
69
+ consultDestinationType: null,
70
+ taskData: createTaskData(),
71
+ uiControlConfig: {agentId: 'agent-123'},
72
+ uiControls: {
73
+ main: {
74
+ accept: {isVisible: false, isEnabled: false},
75
+ decline: {isVisible: false, isEnabled: false},
76
+ hold: {isVisible: false, isEnabled: false},
77
+ end: {isVisible: false, isEnabled: false},
78
+ transfer: {isVisible: false, isEnabled: false},
79
+ consult: {isVisible: false, isEnabled: false},
80
+ consultTransfer: {isVisible: false, isEnabled: false},
81
+ endConsult: {isVisible: false, isEnabled: false},
82
+ recording: {isVisible: false, isEnabled: false},
83
+ conference: {isVisible: false, isEnabled: false},
84
+ wrapup: {isVisible: false, isEnabled: false},
85
+ exitConference: {isVisible: false, isEnabled: false},
86
+ transferConference: {isVisible: false, isEnabled: false},
87
+ mergeToConference: {isVisible: false, isEnabled: false},
88
+ switch: {isVisible: false, isEnabled: false},
89
+ mute: {isVisible: false, isEnabled: false},
90
+ },
91
+ consult: {
92
+ accept: {isVisible: false, isEnabled: false},
93
+ decline: {isVisible: false, isEnabled: false},
94
+ hold: {isVisible: false, isEnabled: false},
95
+ end: {isVisible: false, isEnabled: false},
96
+ transfer: {isVisible: false, isEnabled: false},
97
+ consult: {isVisible: false, isEnabled: false},
98
+ consultTransfer: {isVisible: false, isEnabled: false},
99
+ endConsult: {isVisible: false, isEnabled: false},
100
+ recording: {isVisible: false, isEnabled: false},
101
+ conference: {isVisible: false, isEnabled: false},
102
+ wrapup: {isVisible: false, isEnabled: false},
103
+ exitConference: {isVisible: false, isEnabled: false},
104
+ transferConference: {isVisible: false, isEnabled: false},
105
+ mergeToConference: {isVisible: false, isEnabled: false},
106
+ switch: {isVisible: false, isEnabled: false},
107
+ mute: {isVisible: false, isEnabled: false},
108
+ },
109
+ activeLeg: 'main',
110
+ },
111
+ ...overrides,
112
+ } as TaskContext);
113
+
114
+ const createParams = (context: TaskContext, event?: TaskEventPayload): GuardParams => ({
115
+ context,
116
+ event,
117
+ });
118
+
119
+ const createEventWithTaskData = (taskData: TaskData): TaskEventPayload =>
120
+ ({taskData} as TaskEventPayload);
121
+
122
+ describe('Conference Guards', () => {
123
+ it('conferenceInProgressFromEvent returns true with 2+ agents in event', () => {
124
+ const ctx = createContext();
125
+ const taskData = createTaskData({
126
+ interaction: {
127
+ ...createTaskData().interaction,
128
+ participants: {
129
+ a1: createParticipant('a1', 'Agent'),
130
+ a2: createParticipant('a2', 'Agent'),
131
+ },
132
+ media: {
133
+ [INTERACTION_ID]: {
134
+ mediaResourceId: INTERACTION_ID,
135
+ mediaType: 'telephony',
136
+ mediaMgr: 'media-mgr',
137
+ participants: ['a1', 'a2'],
138
+ mType: 'mainCall',
139
+ isHold: false,
140
+ holdTimestamp: null,
141
+ },
142
+ },
143
+ },
144
+ });
145
+ expect(
146
+ guards.conferenceInProgressFromEvent(createParams(ctx, createEventWithTaskData(taskData)))
147
+ ).toBe(true);
148
+ });
149
+
150
+ it('conferenceInProgressFromEvent returns false with 1 agent', () => {
151
+ const ctx = createContext();
152
+ const taskData = createTaskData({
153
+ interaction: {
154
+ ...createTaskData().interaction,
155
+ participants: {
156
+ a1: createParticipant('a1', 'Agent'),
157
+ },
158
+ media: {
159
+ [INTERACTION_ID]: {
160
+ mediaResourceId: INTERACTION_ID,
161
+ mediaType: 'telephony',
162
+ mediaMgr: 'media-mgr',
163
+ participants: ['a1'],
164
+ mType: 'mainCall',
165
+ isHold: false,
166
+ holdTimestamp: null,
167
+ },
168
+ },
169
+ },
170
+ });
171
+ expect(
172
+ guards.conferenceInProgressFromEvent(createParams(ctx, createEventWithTaskData(taskData)))
173
+ ).toBe(false);
174
+ });
175
+ });
176
+
177
+ describe('Consult Guards', () => {
178
+ it('didInitiateConsult returns true when consultingAgentId matches', () => {
179
+ const ctx = createContext();
180
+ const taskData = createTaskData({
181
+ consultingAgentId: 'agent-123',
182
+ isConsulted: false,
183
+ });
184
+ expect(guards.didInitiateConsult(createParams(ctx, createEventWithTaskData(taskData)))).toBe(
185
+ true
186
+ );
187
+ });
188
+
189
+ it('didInitiateConsult returns false when isConsulted is true', () => {
190
+ const ctx = createContext();
191
+ const taskData = createTaskData({isConsulted: true});
192
+ expect(guards.didInitiateConsult(createParams(ctx, createEventWithTaskData(taskData)))).toBe(
193
+ false
194
+ );
195
+ });
196
+
197
+ it('isInteractionConsulting returns true for pending self consult on hydrate', () => {
198
+ const ctx = createContext();
199
+ const taskData = createTaskData({
200
+ isConsulted: false,
201
+ interaction: {
202
+ ...createTaskData().interaction,
203
+ state: 'conference',
204
+ participants: {
205
+ 'agent-123': {
206
+ ...createParticipant('agent-123', 'Agent'),
207
+ consultState: 'consultInitiated',
208
+ isConsulted: false,
209
+ },
210
+ 'agent-456': {
211
+ ...createParticipant('agent-456', 'Agent'),
212
+ hasJoined: false,
213
+ consultState: 'consultReserved',
214
+ isConsulted: true,
215
+ },
216
+ },
217
+ media: {
218
+ [INTERACTION_ID]: {
219
+ mediaResourceId: INTERACTION_ID,
220
+ mediaType: 'telephony',
221
+ mediaMgr: 'media-mgr',
222
+ participants: ['agent-123'],
223
+ mType: 'mainCall',
224
+ isHold: true,
225
+ holdTimestamp: Date.now(),
226
+ },
227
+ 'consult-media': {
228
+ mediaResourceId: 'consult-media',
229
+ mediaType: 'telephony',
230
+ mediaMgr: 'media-mgr',
231
+ participants: ['agent-123', 'agent-456'],
232
+ mType: 'consult',
233
+ isHold: false,
234
+ holdTimestamp: null,
235
+ },
236
+ },
237
+ } as any,
238
+ });
239
+ expect(
240
+ guards.isInteractionConsulting(createParams(ctx, createEventWithTaskData(taskData)))
241
+ ).toBe(true);
242
+ });
243
+
244
+ it('isInteractionConsulting returns false for consulted agent pending consult state', () => {
245
+ const ctx = createContext();
246
+ const taskData = createTaskData({
247
+ isConsulted: true,
248
+ interaction: {
249
+ ...createTaskData().interaction,
250
+ state: 'conference',
251
+ participants: {
252
+ 'agent-123': {
253
+ ...createParticipant('agent-123', 'Agent'),
254
+ consultState: 'consultInitiated',
255
+ isConsulted: true,
256
+ },
257
+ },
258
+ media: {
259
+ [INTERACTION_ID]: {
260
+ mediaResourceId: INTERACTION_ID,
261
+ mediaType: 'telephony',
262
+ mediaMgr: 'media-mgr',
263
+ participants: ['agent-123'],
264
+ mType: 'mainCall',
265
+ isHold: true,
266
+ holdTimestamp: Date.now(),
267
+ },
268
+ 'consult-media': {
269
+ mediaResourceId: 'consult-media',
270
+ mediaType: 'telephony',
271
+ mediaMgr: 'media-mgr',
272
+ participants: ['agent-123'],
273
+ mType: 'consult',
274
+ isHold: false,
275
+ holdTimestamp: null,
276
+ },
277
+ },
278
+ } as any,
279
+ });
280
+ expect(
281
+ guards.isInteractionConsulting(createParams(ctx, createEventWithTaskData(taskData)))
282
+ ).toBe(false);
283
+ });
284
+ });
285
+
286
+ describe('Wrap-up Guards', () => {
287
+ it('shouldWrapUp returns true for owner', () => {
288
+ const ctx = createContext();
289
+ const taskData = createTaskData({
290
+ interaction: {
291
+ ...createTaskData().interaction,
292
+ owner: 'agent-123',
293
+ },
294
+ });
295
+ expect(guards.shouldWrapUp(createParams(ctx, createEventWithTaskData(taskData)))).toBe(true);
296
+ });
297
+
298
+ it('shouldWrapUp returns false for non-owner', () => {
299
+ const ctx = createContext();
300
+ const taskData = createTaskData({
301
+ interaction: {
302
+ ...createTaskData().interaction,
303
+ owner: 'other-agent',
304
+ },
305
+ });
306
+ expect(guards.shouldWrapUp(createParams(ctx, createEventWithTaskData(taskData)))).toBe(false);
307
+ });
308
+
309
+ it('shouldWrapUp returns true when agentsPendingWrapUp includes agent', () => {
310
+ const ctx = createContext();
311
+ const taskData = createTaskData({
312
+ agentsPendingWrapUp: ['agent-123', 'other-agent'],
313
+ } as Partial<TaskData>);
314
+ expect(guards.shouldWrapUp(createParams(ctx, createEventWithTaskData(taskData)))).toBe(true);
315
+ });
316
+
317
+ it('shouldWrapUp uses isConsulted fallback when no owner', () => {
318
+ const taskData = createTaskData({
319
+ isConsulted: false,
320
+ interaction: {
321
+ ...createTaskData().interaction,
322
+ owner: undefined as unknown as string, // No owner
323
+ },
324
+ });
325
+ const ctx = createContext({taskData});
326
+ expect(guards.shouldWrapUp(createParams(ctx, createEventWithTaskData(taskData)))).toBe(true);
327
+ });
328
+ });
329
+
330
+ describe('isPassiveConferenceTransferObserver', () => {
331
+ it('returns false when transfer was initiated via widgets', () => {
332
+ const ctx = createContext({transferConferenceRequested: true, consultInitiator: false});
333
+ expect(
334
+ guards.isPassiveConferenceTransferObserver(createParams(ctx, createEventWithTaskData()))
335
+ ).toBe(false);
336
+ });
337
+
338
+ it('returns false when this agent initiated the consult transfer', () => {
339
+ const ctx = createContext({transferConferenceRequested: false, consultInitiator: false});
340
+ const taskData = createTaskData({consultingAgentId: 'agent-123'});
341
+ expect(
342
+ guards.isPassiveConferenceTransferObserver(createParams(ctx, createEventWithTaskData(taskData)))
343
+ ).toBe(false);
344
+ });
345
+
346
+ it('returns true when only stale consultInitiator is set without consultingAgentId evidence', () => {
347
+ const ctx = createContext({transferConferenceRequested: false, consultInitiator: true});
348
+ expect(
349
+ guards.isPassiveConferenceTransferObserver(createParams(ctx, createEventWithTaskData()))
350
+ ).toBe(true);
351
+ });
352
+
353
+ it('returns false when self is absent from participants snapshot', () => {
354
+ const ctx = createContext({consultInitiator: false});
355
+ const taskData = createTaskData({
356
+ interaction: {
357
+ ...createTaskData().interaction,
358
+ participants: {
359
+ 'other-agent': createParticipant('other-agent', 'Agent'),
360
+ },
361
+ },
362
+ });
363
+ expect(
364
+ guards.isPassiveConferenceTransferObserver(createParams(ctx, createEventWithTaskData(taskData)))
365
+ ).toBe(false);
366
+ });
367
+
368
+ it('returns false when self hasLeft in participants snapshot', () => {
369
+ const ctx = createContext({consultInitiator: false});
370
+ const taskData = createTaskData({
371
+ interaction: {
372
+ ...createTaskData().interaction,
373
+ participants: {
374
+ 'agent-123': createParticipant('agent-123', 'Agent', true),
375
+ },
376
+ },
377
+ });
378
+ expect(
379
+ guards.isPassiveConferenceTransferObserver(createParams(ctx, createEventWithTaskData(taskData)))
380
+ ).toBe(false);
381
+ });
382
+
383
+ it('returns true for passive observer still in the conference', () => {
384
+ const ctx = createContext({transferConferenceRequested: false, consultInitiator: false});
385
+ const taskData = createTaskData({
386
+ interaction: {
387
+ ...createTaskData().interaction,
388
+ participants: {
389
+ 'agent-123': createParticipant('agent-123', 'Agent'),
390
+ 'other-agent': createParticipant('other-agent', 'Agent'),
391
+ },
392
+ },
393
+ });
394
+ expect(
395
+ guards.isPassiveConferenceTransferObserver(createParams(ctx, createEventWithTaskData(taskData)))
396
+ ).toBe(true);
397
+ });
398
+
399
+ it('returns false when secondary agent initiated transfer via payload consultingAgentId', () => {
400
+ const ctx = createContext({transferConferenceRequested: false, consultInitiator: false});
401
+ const taskData = createTaskData({
402
+ isConsulted: true,
403
+ consultingAgentId: 'agent-123',
404
+ interaction: {
405
+ ...createTaskData().interaction,
406
+ participants: {
407
+ 'agent-123': {...createParticipant('agent-123', 'Agent'), consultState: 'consulting'},
408
+ },
409
+ media: {
410
+ ...createTaskData().interaction!.media,
411
+ 'consult-media': {
412
+ mediaResourceId: 'consult-media',
413
+ mediaType: 'telephony',
414
+ mediaMgr: 'media-mgr',
415
+ participants: ['agent-123', 'agent-456'],
416
+ mType: 'consult',
417
+ isHold: false,
418
+ holdTimestamp: null,
419
+ },
420
+ },
421
+ },
422
+ });
423
+ expect(
424
+ guards.isPassiveConferenceTransferObserver(createParams(ctx, createEventWithTaskData(taskData)))
425
+ ).toBe(false);
426
+ });
427
+ });
428
+
429
+ describe('isSelfConferenceTransferInitiator', () => {
430
+ it('returns true when consultingAgentId matches self even if isConsulted is true', () => {
431
+ const ctx = createContext({consultInitiator: false, transferConferenceRequested: false});
432
+ const taskData = createTaskData({
433
+ isConsulted: true,
434
+ consultingAgentId: 'agent-123',
435
+ });
436
+ expect(
437
+ guards.isSelfConferenceTransferInitiator(createParams(ctx, createEventWithTaskData(taskData)))
438
+ ).toBe(true);
439
+ });
440
+
441
+ it('returns false when consultee has consultState consulting but consultingAgentId is another agent', () => {
442
+ const ctx = createContext({consultInitiator: false, transferConferenceRequested: false});
443
+ const taskData = createTaskData({
444
+ isConsulted: true,
445
+ consultingAgentId: 'agent-456',
446
+ interaction: {
447
+ ...createTaskData().interaction,
448
+ participants: {
449
+ 'agent-123': {...createParticipant('agent-123', 'Agent'), consultState: 'consulting'},
450
+ },
451
+ media: {
452
+ [INTERACTION_ID]: createTaskData().interaction!.media![INTERACTION_ID],
453
+ 'consult-media': {
454
+ mediaResourceId: 'consult-media',
455
+ mediaType: 'telephony',
456
+ mediaMgr: 'media-mgr',
457
+ participants: ['agent-123', 'agent-456'],
458
+ mType: 'consult',
459
+ isHold: false,
460
+ holdTimestamp: null,
461
+ },
462
+ },
463
+ },
464
+ });
465
+ expect(
466
+ guards.isSelfConferenceTransferInitiator(createParams(ctx, createEventWithTaskData(taskData)))
467
+ ).toBe(false);
468
+ });
469
+
470
+ it('returns false for passive observer without consult signals', () => {
471
+ const ctx = createContext({consultInitiator: false, transferConferenceRequested: false});
472
+ const taskData = createTaskData({
473
+ isConsulted: true,
474
+ interaction: {
475
+ ...createTaskData().interaction,
476
+ participants: {
477
+ 'agent-123': createParticipant('agent-123', 'Agent'),
478
+ },
479
+ },
480
+ });
481
+ expect(
482
+ guards.isSelfConferenceTransferInitiator(createParams(ctx, createEventWithTaskData(taskData)))
483
+ ).toBe(false);
484
+ });
485
+ });
486
+
487
+ describe('Hydration Guards - isInteractionConsulting', () => {
488
+ it('returns true when interaction state is consulting', () => {
489
+ const ctx = createContext();
490
+ const taskData = createTaskData({
491
+ interaction: {
492
+ ...createTaskData().interaction,
493
+ state: 'consulting',
494
+ },
495
+ });
496
+ expect(
497
+ guards.isInteractionConsulting(createParams(ctx, createEventWithTaskData(taskData)))
498
+ ).toBe(true);
499
+ });
500
+
501
+ it('returns true for EP_DN consulted agent (state=connected, CPD relationshipType=consult)', () => {
502
+ const ctx = createContext();
503
+ const taskData = createTaskData({
504
+ interaction: {
505
+ ...createTaskData().interaction,
506
+ state: 'connected',
507
+ callProcessingDetails: {
508
+ ...createTaskData().interaction!.callProcessingDetails,
509
+ relationshipType: 'consult',
510
+ },
511
+ },
512
+ });
513
+ expect(
514
+ guards.isInteractionConsulting(createParams(ctx, createEventWithTaskData(taskData)))
515
+ ).toBe(true);
516
+ });
517
+
518
+ it('returns true when post_call with active consult (consultState=consulting + consult media)', () => {
519
+ const ctx = createContext();
520
+ const taskData = createTaskData({
521
+ interaction: {
522
+ ...createTaskData().interaction,
523
+ state: 'post_call',
524
+ participants: {
525
+ 'agent-123': {
526
+ ...createParticipant('agent-123', 'Agent'),
527
+ consultState: 'consulting',
528
+ },
529
+ },
530
+ media: {
531
+ [INTERACTION_ID]: {
532
+ mediaResourceId: INTERACTION_ID,
533
+ mediaType: 'telephony',
534
+ mediaMgr: 'media-mgr',
535
+ participants: ['agent-123'],
536
+ mType: 'mainCall',
537
+ isHold: false,
538
+ holdTimestamp: null,
539
+ },
540
+ 'consult-media': {
541
+ mediaResourceId: 'consult-media',
542
+ mediaType: 'telephony',
543
+ mediaMgr: 'media-mgr',
544
+ participants: ['agent-123', 'agent-2'],
545
+ mType: 'consult',
546
+ isHold: false,
547
+ holdTimestamp: null,
548
+ },
549
+ },
550
+ },
551
+ });
552
+ expect(
553
+ guards.isInteractionConsulting(createParams(ctx, createEventWithTaskData(taskData)))
554
+ ).toBe(true);
555
+ });
556
+
557
+ it('returns false for post_call without consult media', () => {
558
+ const ctx = createContext();
559
+ const taskData = createTaskData({
560
+ interaction: {
561
+ ...createTaskData().interaction,
562
+ state: 'post_call',
563
+ participants: {
564
+ 'agent-123': {
565
+ ...createParticipant('agent-123', 'Agent'),
566
+ consultState: undefined,
567
+ },
568
+ },
569
+ },
570
+ });
571
+ expect(
572
+ guards.isInteractionConsulting(createParams(ctx, createEventWithTaskData(taskData)))
573
+ ).toBe(false);
574
+ });
575
+
576
+ it('returns false for plain connected state without consult CPD', () => {
577
+ const ctx = createContext();
578
+ const taskData = createTaskData({
579
+ interaction: {
580
+ ...createTaskData().interaction,
581
+ state: 'connected',
582
+ },
583
+ });
584
+ expect(
585
+ guards.isInteractionConsulting(createParams(ctx, createEventWithTaskData(taskData)))
586
+ ).toBe(false);
587
+ });
588
+ });
589
+
590
+ describe('Server State Guards', () => {
591
+ it('isPrimaryMediaOnHold returns true when media isHold is true', () => {
592
+ const ctx = createContext();
593
+ const taskData = createTaskData({
594
+ interaction: {
595
+ ...createTaskData().interaction,
596
+ media: {
597
+ [INTERACTION_ID]: {
598
+ mediaResourceId: INTERACTION_ID,
599
+ mediaType: 'telephony',
600
+ mediaMgr: 'media-mgr',
601
+ participants: ['agent-123'],
602
+ mType: 'mainCall',
603
+ isHold: true,
604
+ holdTimestamp: Date.now(),
605
+ },
606
+ },
607
+ },
608
+ });
609
+ expect(
610
+ guards.isPrimaryMediaOnHold(createParams(ctx, createEventWithTaskData(taskData)))
611
+ ).toBe(true);
612
+ });
613
+
614
+ it('isPrimaryMediaOnHold returns false when media isHold is false', () => {
615
+ const ctx = createContext();
616
+ const taskData = createTaskData({
617
+ interaction: {
618
+ ...createTaskData().interaction,
619
+ media: {
620
+ [INTERACTION_ID]: {
621
+ mediaResourceId: INTERACTION_ID,
622
+ mediaType: 'telephony',
623
+ mediaMgr: 'media-mgr',
624
+ participants: ['agent-123'],
625
+ mType: 'mainCall',
626
+ isHold: false,
627
+ holdTimestamp: null,
628
+ },
629
+ },
630
+ },
631
+ });
632
+ expect(
633
+ guards.isPrimaryMediaOnHold(createParams(ctx, createEventWithTaskData(taskData)))
634
+ ).toBe(false);
635
+ });
636
+ });
637
+ });
@@ -0,0 +1,18 @@
1
+ import {isEventOfType} from '../../../../../../src/services/task/state-machine/types';
2
+ import {TaskEvent} from '../../../../../../src/services/task/state-machine';
3
+
4
+ describe('state-machine types', () => {
5
+ describe('isEventOfType', () => {
6
+ it('returns true for matching type', () => {
7
+ const event = {type: TaskEvent.TASK_INCOMING, taskData: {interactionId: 'id'} as any};
8
+ expect(isEventOfType(event as any, TaskEvent.TASK_INCOMING)).toBe(true);
9
+ });
10
+
11
+ it('returns false for non-matching type or undefined', () => {
12
+ const event = {type: TaskEvent.TASK_INCOMING, taskData: {interactionId: 'id'} as any};
13
+ expect(isEventOfType(event as any, TaskEvent.ASSIGN)).toBe(false);
14
+ expect(isEventOfType(undefined, TaskEvent.ASSIGN)).toBe(false);
15
+ });
16
+ });
17
+ });
18
+