Skip to content

Conversation

@arun3528
Copy link
Collaborator

@arun3528 arun3528 commented Nov 4, 2025

COMPLETES #< INSERT LINK TO ISSUE >

This pull request addresses

< DESCRIBE THE CONTEXT OF THE ISSUE >

by making the following changes

< DESCRIBE YOUR CHANGES >

Change Type

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Tooling change
  • Internal code refactor

The following scenarios were tested

< ENUMERATE TESTS PERFORMED, WHETHER MANUAL OR AUTOMATED >

The GAI Coding Policy And Copyright Annotation Best Practices

  • GAI was not used (or, no additional notation is required)
  • Code was generated entirely by GAI
  • GAI was used to create a draft that was subsequently customized or modified
  • Coder created a draft manually that was non-substantively modified by GAI (e.g., refactoring was performed by GAI on manually written code)
  • Tool used for AI assistance (GitHub Copilot / Other - specify)
    • Github Copilot
    • Other - Please Specify
  • This PR is related to
    • Feature
    • Defect fix
    • Tech Debt
    • Automation

I certified that

  • I have read and followed contributing guidelines
  • I discussed changes with code owners prior to submitting this pull request
  • I have not skipped any automated checks
  • All existing and new tests passed
  • I have updated the documentation accordingly

Make sure to have followed the contributing guidelines before submitting.

on: {
[TaskEvent.UNHOLD]: {
target: TaskState.CONNECTED,
cond: 'canResume',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we really need canResume guard check here... i feel it should be allowed irrsepctive right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we only allow resume when its on hold , we need some check on the SDK not on the widgets ,

this normally could be enhanced adding intermediate state as well

public data: TaskData;
public webCallMap: Record<TaskId, CallId>;
public taskUiControls: TaskUIControls;
public state: any;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One small doubt, which I think we can discuss.
I like that the state is public... so for some cases, say HELD state, is it ok if the user directly accesses the state from this object rather than storing it themsleves? That ways this state can be the source of truth.
Wanted to know if that was also the thinking here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ya @adhmenon that was the idea we just expose current state , state machine and intermediate state only gets used internally

/**
* Can accept if in OFFERED or OFFERED_CONSULT state
*/
canAccept: (context: TaskContext, event: any, meta: {state: {value: StateValue}}): boolean => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not recommend using any in the code, so please assign a type to the event

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed it

* Validate state transition
* Returns true if transition from current state to target state is valid
*/
export function isValidTransition(currentState: TaskState, targetState: TaskState): boolean {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need separate function to validate the state transitions? State Machine takes care of validating them on its own and blocks the transitions from happening if not valid

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it was just a helper method, not getting used will remove it

// Determine UI control states based on current state and context
const isOffered = state.matches(TaskState.OFFERED) || state.matches(TaskState.OFFERED_CONSULT);
const isConnected = state.matches(TaskState.CONNECTED);
const isHeld = state.matches(TaskState.HELD);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is context being used here to determine the UI control state? From what I understand we are only verifying if the current state matches the passed state

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no context is not getting used here. just the state , context only comes into picture for complex flows , there is only 3 or 4 or them , will be used more in conference

* task.hold().then(()=>{}).catch(()=>{})
* ```
* */
public async hold(): Promise<TaskResponse> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why have we changed this ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was a issue we expose hold in webrtc and task but voice only has holdResume and not hold, resume , not sure how we missed it , whats your thoughts on it ?

const shouldHold = !this.data.interaction.media[this.data.mediaResourceId].isHold;

// Validate operation is allowed in current state
const state = this.stateMachineService?.state;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the significance of this code, if we have state machine in place that should block the API invocation too instead of we need to add if else condition to throw error. This should be controlled via state machine itself

*/
public async pauseRecording(): Promise<TaskResponse> {
// Validate recording is active
const context = this.stateMachineService?.state?.context;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here

public async consult(consultPayload?: ConsultPayload): Promise<TaskResponse> {
// Validate consult is allowed
const state = this.stateMachineService?.state;
const canConsult =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a canConsult guard as well as we are calculating the same thing here again using state machine. Feels redundant

/**
* Can only start conference if consult destination agent has joined
*/
canStartConference: (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conditions that we have in these guards can be just be directly put in the actions itself that are being invoked from each of the state transitions and update context based on conditions

});

// Send event to task's state machine
taskWithStateMachine.stateMachineService.send(stateMachineEvent);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change the second argument to payload

public async pauseRecording(): Promise<TaskResponse> {
this.unsupportedMethodError('pauseRecording');

return Promise.reject(new Error('pauseRecording not supported for this channel type'));
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use throw error rather then returning
Promise.reject

* ```
*/
public get taskUiControls(): TaskUIActions {
// Convert computed UI controls to TaskActionControl objects for backward compatibility
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this , make sure we publish event on each UI control change , its will have UI control object

*
* @returns UI control states for all task actions
*/
protected computeUIControls(): TaskUIControls {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

returned object can be a loop one liner

* This replaces the old updateUIControlsFromState() method.
* Returns plain objects instead of mutating taskUiControls.
*/
protected computeUIControls(): import('../Task').TaskUIControls {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix this

// Consult transfer: visible during consulting
consultTransfer: {
visible: isConsulting,
enabled: true,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enabled/show/visable /display :
supported

check on the naming

// Validate operation is allowed in current state
const state = this.stateMachineService?.state;
if (state) {
const currentState = state.value as TaskState;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move this logic inside the hold function and resume function

* Create initial context for a new task
*/
export function createInitialContext(): TaskContext {
return {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doc on how the context can be used for a specific feature , comment out all the unused values

* Initialize task with offer data
*/
initializeTask: assign((context: TaskContext, event: TaskEventPayload) => {
if (isEventOfType(event, TaskEvent.OFFER) || isEventOfType(event, TaskEvent.OFFER_CONSULT)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't seem to be invoking this action apart from these 2 events only so why do we have the need to add this condition here ?


[TaskState.OFFERED]: {
on: {
[TaskEvent.ACCEPT]: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the difference between ACCEPT and ASSIGN ? Why do we need 2 events here ?

target: TaskState.CONNECTED,
actions: ['updateTaskData'],
},
[TaskEvent.RONA]: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 2 events also can be one single event

[TaskEvent.ACCEPT]: {
target: TaskState.CONSULTING,
},
[TaskEvent.RONA]: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here

[TaskEvent.CONSULTING_ACTIVE]: {
actions: ['setConsultAgentJoined'],
},
[TaskEvent.START_CONFERENCE]: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

start_conference and merge conference are not very different from each other so do we need separate states for it

private registerTaskListeners() {
this.webSocketManager.on('message', (event) => {
const payload = JSON.parse(event);
if (payload?.keepalive === 'true' || payload?.keepalive === true) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we need this additional condition ?

return;
}
if (payload?.data?.interaction) {
payload.data = normalizeTaskData(payload.data);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this normalizing required ?

this.updateTaskData(task, payload.data);
task.emit(TASK_EVENTS.TASK_RECORDING_RESUME_FAILED, task);
break;
case CC_EVENTS.AGENT_CONSULT_CONFERENCING:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we remove this because we are going to add conference separately ? Maybe commenting would be better than removing

this.emit(TASK_EVENTS.TASK_INCOMING, task);
}
break;
case CC_EVENTS.AGENT_OFFER_CONTACT:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we even need other event handling here now that we have state machine taking care of it

return result;
} catch (error) {
// Send failure event to transition back to previous state
if (this.stateMachineService) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this if condition required everywhere ?

@Kesari3008
Copy link
Contributor

Please make the PR ready for review so can run the pipeline too on the changes

@arun3528
Copy link
Collaborator Author

add check for normalizer to pass through if its a boolean @rajeshtezu

@arun3528
Copy link
Collaborator Author

remove the callbacks from the actions and add enum , rather then callbackl , add one callback

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants