You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Migration Doc 012: Task Lifecycle Flows — Complete Old vs New
Purpose
This document traces every task scenario from start to finish, showing exactly what happens at each step in both the old and new approach. Each flow maps:
1. Task enters wrapup state (Flow 6 steps 1-7)
2. Hook: useEffect detects currentTask.autoWrapup && controlVisibility.wrapup
3. Hook: reads currentTask.autoWrapup.getTimeLeftSeconds()
4. Hook: setInterval every 1s → decrements secondsUntilAutoWrapup
5. Widget: AutoWrapupTimer component shows countdown
6. If user clicks Cancel: cancelAutoWrapup() → currentTask.cancelAutoWrapupTimer()
7. If timer reaches 0: SDK auto-submits wrapup with default reason
New Flow
1. Task enters WRAPPING_UP state (Flow 6 new steps 1-8)
2. Hook: useEffect detects currentTask.autoWrapup && controls.wrapup.isVisible
(changed: controlVisibility.wrapup → controls.wrapup)
3-7. Same as old — auto-wrapup is a widget-layer timer concern, SDK unchanged
SDK context (recordingInProgress) + widget local state
Recording button visibility
getPauseResumeRecordingButtonVisibility()
controls.recording from SDK
Recording indicator
Separate recordingIndicator control
Merged into recording control
Flow 9: Blind Transfer
Old Flow
1. User clicks Transfer, selects destination
2. Hook: transferCall(to, type) → currentTask.transfer({to, destinationType})
3. SDK: API call
4. WebSocket: AgentBlindTransferred
5. SDK emits: (leads to task:end or task:wrapup depending on config)
6. Store: refreshTaskList()
7. UI Controls: getControlsVisibility() → wrapup or all disabled
New Flow
1. User clicks Transfer, selects destination
2. Hook: transferCall(to, type) → currentTask.transfer({to, destinationType})
3. SDK: API call
4. WebSocket: AgentBlindTransferred → TaskEvent.TRANSFER_SUCCESS
5. SDK: guards: shouldWrapUpOrIsInitiator?
6. SDK: State → WRAPPING_UP (if wrapup required) or stays CONNECTED
7. SDK: computeUIControls for new state
8. SDK emits: task:end/task:wrapup, task:ui-controls-updated
Flow 10: Digital Task (Chat/Email) — Accept → End → Wrapup
Old Flow
1. WebSocket: AgentContactReserved (mediaType: chat)
2. SDK emits: task:incoming
3. Store: handleIncomingTask()
4. UI Controls: getAcceptButtonVisibility():
→ isBrowser && isDigitalChannel → accept visible
→ decline NOT visible (digital channels)
5. User clicks Accept
6. incomingTask.accept() → task:assigned
7. UI Controls: getControlsVisibility():
→ end visible, transfer visible, wrapup hidden
→ hold/mute/consult/conference/recording: all hidden (digital)
8. User clicks End
9. currentTask.end() → task:end, task:wrapup
10. UI Controls: wrapup visible (if wrapUpRequired)
New Flow
1. WebSocket: AgentContactReserved (mediaType: chat)
2. SDK: State: IDLE → OFFERED, channelType: DIGITAL
3. SDK: computeDigitalUIControls(OFFERED) → accept visible, decline hidden
4. SDK emits: task:incoming, task:ui-controls-updated
5. User clicks Accept
6. incomingTask.accept() → ASSIGN → State: OFFERED → CONNECTED
7. SDK: computeDigitalUIControls(CONNECTED) → end visible, transfer visible
→ hold/mute/consult/conference/recording: all disabled (digital)
8. User clicks End
9. currentTask.end() → CONTACT_ENDED → WRAPPING_UP
10. SDK: computeDigitalUIControls(WRAPPING_UP) → wrapup visible
Key Difference
Aspect
Old
New
Channel detection
Widget checks mediaType === 'telephony' vs chat/email
SDK checks channelType: VOICE vs DIGITAL
Digital controls
getControlsVisibility() with isCall=false, isDigitalChannel=true
computeDigitalUIControls() — much simpler logic
Controls shown
Same end result
Same end result (accept, end, transfer, wrapup only)
Flow 11: Page Refresh → Hydration
Old Flow
1. Agent refreshes browser page
2. SDK reconnects, receives AgentContact for active task
3. SDK emits: task:hydrate
4. Store: handleTaskHydrate() → refreshTaskList() → cc.taskManager.getAllTasks()
5. Store: sets currentTask, taskList from fetched data
6. Widgets: observer re-renders with restored task
7. UI Controls: getControlsVisibility() computes from raw task data
→ Must correctly derive: held state, consult state, conference state
→ Error-prone: depends on raw interaction data being complete
New Flow
1. Agent refreshes browser page
2. SDK reconnects, receives AgentContact for active task
3. SDK: TaskManager sends TaskEvent.HYDRATE with task data
4. SDK: State machine guards determine correct state:
→ isInteractionTerminated? → WRAPPING_UP
→ isInteractionConsulting? → CONSULTING
→ isInteractionHeld? → HELD
→ isInteractionConnected? → CONNECTED
→ isConferencingByParticipants? → CONFERENCING
→ default → IDLE (data update only)
5. SDK: computeUIControls for resolved state → correct controls for restored state
6. SDK emits: task:hydrate, task:ui-controls-updated
7. Store: handleTaskHydrate() → set currentTask, taskList
8. Widgets: observer re-renders; controls from task.uiControls are correct
Key Difference
Aspect
Old
New
State recovery
refreshTaskList() + getControlsVisibility() from raw data
State machine guards determine correct state
Reliability
Can show wrong controls if interaction data is incomplete
Guards explicitly check each condition; predictable
Conference recovery
Depends on isConferenceInProgress flag in data
Guard: isConferencingByParticipants counts agents
Flow 12: Outdial → New Task
Old Flow
1. User enters number, clicks Dial
2. Hook: startOutdial(destination, origin) → cc.startOutdial(destination, origin)
3. SDK: API call (CC-level, not task-level)
4. Backend creates task, sends AgentContactReserved
5. Flow continues as Flow 1 (Incoming → Accept → Connected)
New Flow
Identical — outdial initiation is CC-level. Once the task is created,
the state machine takes over and flows follow Flow 1 new approach.
No changes needed.
Flow 13: Consult Transfer (from Consulting State)
Old Flow
1. In consulting state (Flow 4 steps 1-11)
2. User clicks Consult Transfer
3. Hook: consultTransfer() checks currentTask.data.isConferenceInProgress:
→ false: currentTask.consultTransfer()
→ true: currentTask.transferConference()
4. SDK: API call
5. WebSocket: AgentConsultTransferred / AgentConferenceTransferred
6. SDK emits: task events (leads to end/wrapup)
7. Store: refreshTaskList()
8. UI Controls: wrapup or all disabled
New Flow
1. In CONSULTING state (Flow 4 new steps 1-15)
2. User clicks Transfer (transfer control now handles consult transfer)
3. Hook: consultTransfer() checks controls.transferConference.isVisible:
→ false: currentTask.consultTransfer()
→ true: currentTask.transferConference()
4. SDK: API call
5. WebSocket: → TaskEvent.TRANSFER_SUCCESS or TRANSFER_CONFERENCE_SUCCESS
6. SDK: State → WRAPPING_UP or TERMINATED
7. SDK: computeUIControls for new state
8. SDK emits: events + task:ui-controls-updated
Key Difference
Aspect
Old
New
Conference check
currentTask.data.isConferenceInProgress
controls.transferConference.isVisible (or keep data check)
Transfer button
Separate consultTransferConsult control
Unified transfer control handles all transfer types
Flow 14: Switch Between Main Call and Consult Call
Old Flow
1. In consulting state, agent is on consult leg
2. User clicks "Switch to Main Call"
3. Hook: switchToMainCall():
→ currentTask.resume(findMediaResourceId(task, 'consult'))
(resumes consult media → puts consult on hold → main call active)
4. WebSocket: AgentContactHeld (consult) + AgentContactUnheld (main)
5. SDK emits: task:hold, task:resume
6. Store: refreshTaskList() (twice)
7. UI Controls: getControlsVisibility():
→ consultCallHeld = true → switchToConsult visible
→ switchToMainCall hidden
New Flow
1. In CONSULTING state, agent is on consult leg
2. User clicks "Switch to Main Call"
3. Hook: switchToMainCall():
→ currentTask.resume(findMediaResourceId(task, 'consult'))
4. SDK: HOLD_INITIATED / UNHOLD_INITIATED → state machine tracks
5. SDK: context.consultCallHeld updated
6. SDK: computeUIControls(CONSULTING, updated context):
→ switchToConsult visible (consult is now held)
→ switchToMainCall hidden
7. SDK emits: task:hold, task:resume, task:ui-controls-updated
8. Hook: controls updated via listener
Key Difference
Aspect
Old
New
consultCallHeld tracking
findHoldStatus(task, 'consult', agentId)
context.consultCallHeld in state machine
Controls update
After 2x refreshTaskList()
Single task:ui-controls-updated after state settles
State Machine States → Widget Controls Summary
TaskState (New)
Old Equivalent
Controls Visible
IDLE
No task / before incoming
All disabled
OFFERED
Incoming task shown
accept, decline (WebRTC voice); accept only (digital)
Every action now follows: User action → SDK method → State machine event(s) → task.uiControls recomputed → task:ui-controls-updated emitted. Widgets never need to compute controls themselves.