-
Notifications
You must be signed in to change notification settings - Fork 390
fix(handle-auto-answer): implement-auto-answer-for-desktop-calls #4571
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 14 commits
711be1f
c0a934e
78de06b
ef43a93
dbcc9f6
e0c2929
14b86e4
04e2349
3766d84
4baf776
b1ba9a4
9bfb218
ae94cf9
c3b13d4
20abfa9
7bb2d37
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,7 @@ import { | |
| isParticipantInMainInteraction, | ||
| isPrimary, | ||
| isSecondaryEpDnAgent, | ||
| shouldAutoAnswerTask, | ||
| } from './TaskUtils'; | ||
|
|
||
| /** @internal */ | ||
|
|
@@ -36,6 +37,7 @@ export default class TaskManager extends EventEmitter { | |
| private static taskManager; | ||
| private wrapupData: WrapupData; | ||
| private agentId: string; | ||
| private webRtcEnabled: boolean; | ||
| /** | ||
| * @param contact - Routing Contact layer. Talks to AQMReq layer to convert events to promises | ||
| * @param webCallingService - Webrtc Service Layer | ||
|
|
@@ -73,6 +75,10 @@ export default class TaskManager extends EventEmitter { | |
| return this.agentId; | ||
| } | ||
|
|
||
| public setWebRtcEnabled(webRtcEnabled: boolean) { | ||
| this.webRtcEnabled = webRtcEnabled; | ||
| } | ||
|
|
||
| private handleIncomingWebCall = (call: ICall) => { | ||
| const currentTask = Object.values(this.taskCollection).find( | ||
| (task) => task.data.interaction.mediaType === 'telephony' | ||
|
|
@@ -130,6 +136,14 @@ export default class TaskManager extends EventEmitter { | |
| interactionId: payload.data.interactionId, | ||
| }); | ||
|
|
||
| // Check if auto-answer should happen for this task | ||
| const shouldAutoAnswer = shouldAutoAnswerTask( | ||
| payload.data, | ||
| this.agentId, | ||
| this.webCallingService.loginOption, | ||
| this.webRtcEnabled | ||
| ); | ||
|
|
||
| task = new Task( | ||
| this.contact, | ||
| this.webCallingService, | ||
|
|
@@ -138,6 +152,7 @@ export default class TaskManager extends EventEmitter { | |
| wrapUpRequired: | ||
| payload.data.interaction?.participants?.[this.agentId]?.isWrapUp || false, | ||
| isConferenceInProgress: getIsConferenceInProgress(payload.data), | ||
| isAutoAnswering: shouldAutoAnswer, // Set flag before emitting | ||
| }, | ||
| this.wrapupData, | ||
| this.agentId | ||
|
|
@@ -169,13 +184,22 @@ export default class TaskManager extends EventEmitter { | |
| } | ||
| break; | ||
|
|
||
| case CC_EVENTS.AGENT_CONTACT_RESERVED: | ||
| case CC_EVENTS.AGENT_CONTACT_RESERVED: { | ||
| // Check if auto-answer should happen for this task | ||
| const shouldAutoAnswerReserved = shouldAutoAnswerTask( | ||
| payload.data, | ||
| this.agentId, | ||
| this.webCallingService.loginOption, | ||
| this.webRtcEnabled | ||
| ); | ||
|
|
||
| task = new Task( | ||
| this.contact, | ||
| this.webCallingService, | ||
| { | ||
| ...payload.data, | ||
| isConsulted: false, | ||
| isAutoAnswering: shouldAutoAnswerReserved, // Set flag before emitting | ||
| }, | ||
| this.wrapupData, | ||
| this.agentId | ||
|
|
@@ -190,6 +214,7 @@ export default class TaskManager extends EventEmitter { | |
| this.emit(TASK_EVENTS.TASK_INCOMING, task); | ||
| } | ||
| break; | ||
| } | ||
| case CC_EVENTS.AGENT_OFFER_CONTACT: | ||
| // We don't have to emit any event here since this will be result of promise. | ||
| task = this.updateTaskData(task, payload.data); | ||
|
|
@@ -199,6 +224,9 @@ export default class TaskManager extends EventEmitter { | |
| interactionId: payload.data?.interactionId, | ||
| }); | ||
| this.emit(TASK_EVENTS.TASK_OFFER_CONTACT, task); | ||
|
|
||
| // Handle auto-answer for offer contact | ||
| this.handleAutoAnswer(task); | ||
| break; | ||
| case CC_EVENTS.AGENT_OUTBOUND_FAILED: | ||
| task = this.updateTaskData(task, payload.data); | ||
|
|
@@ -308,6 +336,9 @@ export default class TaskManager extends EventEmitter { | |
| isConsulted: true, // This ensures that the task is marked as us being requested for a consult | ||
| }); | ||
| task.emit(TASK_EVENTS.TASK_OFFER_CONSULT, task); | ||
|
|
||
| // Handle auto-answer for consult offer | ||
| this.handleAutoAnswer(task); | ||
| break; | ||
| case CC_EVENTS.AGENT_CONSULTING: | ||
| // Received when agent is in an active consult state | ||
|
|
@@ -545,6 +576,70 @@ export default class TaskManager extends EventEmitter { | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * Handles auto-answer logic for incoming tasks | ||
| * Automatically accepts tasks when isAutoAnswering flag is set | ||
| * The flag is set during task creation based on: | ||
| * 1. WebRTC calls with auto-answer enabled in agent profile | ||
| * 2. Agent-initiated WebRTC outdial calls | ||
| * 3. Agent-initiated digital outbound (Email/SMS) without previous transfers | ||
| * | ||
| * @param task - The task to auto-answer | ||
| * @private | ||
| */ | ||
| private async handleAutoAnswer(task: ITask): Promise<void> { | ||
| if (!task || !task.data || !task.data.isAutoAnswering) { | ||
| return; | ||
| } | ||
|
|
||
| LoggerProxy.info(`Auto-answering task`, { | ||
| module: TASK_MANAGER_FILE, | ||
| method: 'handleAutoAnswer', | ||
| interactionId: task.data.interactionId, | ||
| }); | ||
|
|
||
| try { | ||
| await task.accept(); | ||
| LoggerProxy.info(`Task auto-answered successfully`, { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add metrics for this - it would be good to know if the task was auto answer or not.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed |
||
| module: TASK_MANAGER_FILE, | ||
| method: 'handleAutoAnswer', | ||
| interactionId: task.data.interactionId, | ||
| }); | ||
|
|
||
| // Track successful auto-answer | ||
| this.metricsManager.trackEvent( | ||
| METRIC_EVENT_NAMES.TASK_AUTO_ANSWER_SUCCESS, | ||
| { | ||
| taskId: task.data.interactionId, | ||
| mediaType: task.data.interaction.mediaType, | ||
| isAutoAnswered: true, | ||
| }, | ||
| ['behavioral', 'operational'] | ||
| ); | ||
| } catch (error) { | ||
| // Reset isAutoAnswering flag on failure | ||
| task.updateTaskData({...task.data, isAutoAnswering: false}); | ||
| LoggerProxy.error(`Failed to auto-answer task`, { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same thing here.. have metrics for error for auto answer also.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed |
||
| module: TASK_MANAGER_FILE, | ||
| method: 'handleAutoAnswer', | ||
| interactionId: task.data.interactionId, | ||
| error, | ||
| }); | ||
|
|
||
| // Track auto-answer failure | ||
| this.metricsManager.trackEvent( | ||
| METRIC_EVENT_NAMES.TASK_AUTO_ANSWER_FAILED, | ||
| { | ||
| taskId: task.data.interactionId, | ||
| mediaType: task.data.interaction.mediaType, | ||
| error: error?.message || 'Unknown error', | ||
| isAutoAnswered: false, | ||
| }, | ||
| ['behavioral', 'operational'] | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Handles cleanup of task resources including Desktop/WebRTC call cleanup and task removal | ||
| * @param task - The task to clean up | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe instead of returning, we throw an error here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kept as return — this guard clause handles the normal operation where most tasks don’t have auto-answer enabled. Throwing an error here would treat the expected path as exceptional and introduce unnecessary error handling in the callers.