1212 */
1313
1414import { assign } from 'xstate' ;
15+ import type { ActionFunctionMap , EventObject } from 'xstate' ;
1516import { TaskContext , TaskEventPayload , TaskEvent , UIControlConfig , TaskState } from './types' ;
1617import { TaskData } from '../types' ;
1718import { computeUIControls , getDefaultUIControls } from './uiControlsComputer' ;
1819
20+ type TaskActionsMap = ActionFunctionMap <
21+ TaskContext ,
22+ TaskEventPayload ,
23+ never ,
24+ { type : string ; params : undefined } ,
25+ never ,
26+ never ,
27+ EventObject
28+ > ;
29+
1930type RecordingStateUpdate = Partial <
2031 Pick < TaskContext , 'recordingControlsAvailable' | 'recordingInProgress' >
2132> ;
@@ -28,7 +39,11 @@ const deriveRecordingState = (taskData?: TaskData | null): RecordingStateUpdate
2839 }
2940
3041 const update : RecordingStateUpdate = { } ;
31- const { recordingStarted, recordInProgress} = callProcessingDetails ;
42+ const { recordingStarted, recordInProgress, isPaused} = callProcessingDetails as {
43+ recordingStarted ?: boolean ;
44+ recordInProgress ?: boolean ;
45+ isPaused ?: boolean ;
46+ } ;
3247
3348 if ( recordingStarted !== undefined ) {
3449 update . recordingControlsAvailable = recordingStarted ;
@@ -51,6 +66,11 @@ const deriveRecordingState = (taskData?: TaskData | null): RecordingStateUpdate
5166 update . recordingInProgress = true ;
5267 }
5368
69+ if ( isPaused !== undefined ) {
70+ update . recordingControlsAvailable = true ;
71+ update . recordingInProgress = ! isPaused ;
72+ }
73+
5474 return update ;
5575} ;
5676
@@ -64,10 +84,16 @@ const deriveRecordingState = (taskData?: TaskData | null): RecordingStateUpdate
6484 * single source of truth, while derived values (like recording flags)
6585 * are recalculated here via deriveRecordingState.
6686 */
67- const deriveTaskDataUpdates = ( _context : TaskContext , taskData : TaskData ) => ( {
68- taskData,
69- ...deriveRecordingState ( taskData ) ,
70- } ) ;
87+ const deriveTaskDataUpdates = ( _context : TaskContext , taskData : TaskData | undefined ) =>
88+ taskData
89+ ? {
90+ taskData,
91+ ...deriveRecordingState ( taskData ) ,
92+ }
93+ : { } ;
94+
95+ const getTaskDataFromEvent = ( event ?: TaskEventPayload ) : TaskData | undefined =>
96+ event && typeof event === 'object' ? ( event as any ) . taskData : undefined ;
7197
7298/**
7399 * Create initial context for a new task.
@@ -116,7 +142,7 @@ export function createInitialContext(
116142 * @returns Assign action that updates UI controls
117143 */
118144export function updateUIControls ( currentState : TaskState ) {
119- return assign ( ( context : TaskContext ) => ( {
145+ return assign ( ( { context} : { context : TaskContext } ) => ( {
120146 uiControls : computeUIControls ( currentState , context ) ,
121147 } ) ) ;
122148}
@@ -125,25 +151,19 @@ export function updateUIControls(currentState: TaskState) {
125151 * Action implementations
126152 * These return XState assign actions that update the context
127153 */
128- export const actions = {
154+ export const actions : TaskActionsMap = {
129155 /**
130156 * Initialize task with offer data
131157 */
132- initializeTask : assign ( ( context : TaskContext , event : TaskEventPayload ) => {
133- // Guard not needed in this action because the state machine only references
134- // initializeTask from OFFER/OFFER_CONSULT transitions, both of which carry taskData.
135- const { taskData} = event as Extract < TaskEventPayload , { taskData : TaskData } > ;
136-
137- return deriveTaskDataUpdates ( context , taskData ) ;
158+ initializeTask : assign ( ( { context, event} : { context : TaskContext ; event : TaskEventPayload } ) => {
159+ return deriveTaskDataUpdates ( context , getTaskDataFromEvent ( event ) ) ;
138160 } ) ,
139161
140162 /**
141163 * Update task data from ASSIGN event
142164 */
143- updateTaskData : assign ( ( context : TaskContext , event : TaskEventPayload ) => {
144- const { taskData} = event as Extract < TaskEventPayload , { taskData : TaskData } > ;
145-
146- return deriveTaskDataUpdates ( context , taskData ) ;
165+ updateTaskData : assign ( ( { context, event} : { context : TaskContext ; event : TaskEventPayload } ) => {
166+ return deriveTaskDataUpdates ( context , getTaskDataFromEvent ( event ) ) ;
147167 } ) ,
148168
149169 /**
@@ -156,35 +176,42 @@ export const actions = {
156176 /**
157177 * Set consult destination details
158178 */
159- setConsultDestination : assign ( ( context : TaskContext , event : TaskEventPayload ) => {
160- const consultEvent = event as Extract <
161- TaskEventPayload ,
162- { type : TaskEvent . CONSULT ; destination : string }
163- > ;
179+ setConsultDestination : assign ( ( { event} : { event : TaskEventPayload } ) => {
180+ if ( ! event || event . type !== TaskEvent . CONSULT || ! ( 'destination' in event ) ) {
181+ return { } ;
182+ }
164183
165184 return {
166- consultDestination : consultEvent . destination ,
185+ consultDestination : ( event as { destination : string } ) . destination ,
167186 } ;
168187 } ) ,
169188
170189 /**
171190 * Mark that consult destination agent has joined
172191 */
173- setConsultAgentJoined : assign ( ( context : TaskContext , event : TaskEventPayload ) => {
174- const consultingActive = event as Extract <
175- TaskEventPayload ,
176- { type : TaskEvent . CONSULTING_ACTIVE ; consultDestinationAgentJoined : boolean }
177- > ;
192+ setConsultAgentJoined : assign ( ( { event} : { event : TaskEventPayload } ) => {
193+ if (
194+ ! event ||
195+ event . type !== TaskEvent . CONSULTING_ACTIVE ||
196+ ! ( 'consultDestinationAgentJoined' in event )
197+ ) {
198+ return { } ;
199+ }
178200
179201 return {
180- consultDestinationAgentJoined : consultingActive . consultDestinationAgentJoined ,
202+ consultDestinationAgentJoined : ( event as { consultDestinationAgentJoined : boolean } )
203+ . consultDestinationAgentJoined ,
181204 } ;
182205 } ) ,
183206
184207 /**
185208 * Set recording state
186209 */
187- setRecordingState : assign ( ( context : TaskContext , event : TaskEventPayload ) => {
210+ setRecordingState : assign ( ( { event} : { event : TaskEventPayload } ) => {
211+ if ( ! event || ! ( 'type' in event ) ) {
212+ return { } ;
213+ }
214+
188215 if ( event . type === TaskEvent . PAUSE_RECORDING ) {
189216 return {
190217 recordingControlsAvailable : true ,
@@ -212,13 +239,23 @@ export const actions = {
212239 /**
213240 * Track hold state updates (currently no-op placeholder)
214241 */
215- setHoldState : assign ( ( context : TaskContext , event : TaskEventPayload ) => {
216- const holdEvent = event as Extract <
217- TaskEventPayload ,
218- | { type : TaskEvent . HOLD_SUCCESS ; mediaResourceId : string }
219- | { type : TaskEvent . UNHOLD_SUCCESS ; mediaResourceId : string }
220- > ;
221- const mediaResourceId = holdEvent . mediaResourceId ;
242+ setHoldState : assign ( ( { context, event} : { context : TaskContext ; event : TaskEventPayload } ) => {
243+ if (
244+ ! event ||
245+ ( event . type !== TaskEvent . HOLD_SUCCESS && event . type !== TaskEvent . UNHOLD_SUCCESS )
246+ ) {
247+ return { } ;
248+ }
249+
250+ const mediaResourceId =
251+ 'mediaResourceId' in event
252+ ? ( event as { mediaResourceId ?: string } ) . mediaResourceId
253+ : undefined ;
254+
255+ if ( ! mediaResourceId ) {
256+ return { } ;
257+ }
258+
222259 const interaction = context . taskData ?. interaction ;
223260 const mediaEntry = interaction ?. media ?. [ mediaResourceId ] ;
224261
@@ -230,7 +267,7 @@ export const actions = {
230267 ...interaction . media ,
231268 [ mediaResourceId ] : {
232269 ...mediaEntry ,
233- isHold : holdEvent . type === TaskEvent . HOLD_SUCCESS ,
270+ isHold : event . type === TaskEvent . HOLD_SUCCESS ,
234271 } ,
235272 } ;
236273
0 commit comments