fix(voice): address codex review feedback on Call-model PR

- Guard against delayed conference-start retries rolling a progressed
  call back to ringing (Conference::Manager#mark_ringing!).
- Scope conference-status webhook Call lookup to the resolved inbox
  (VoiceController#find_call_for_conference!).
- Require explicit call_sid on the agent TwiML leg and on
  ConferenceController#create/destroy so multi-call conversations can't
  attach the wrong call (no more most-recent-active fallback).
- Scope StatusUpdateService Call lookup to the webhook's account for
  defense-in-depth.
- Update UPDATE_MESSAGE_CALL_STATUS mutation to match the voice_call
  message by call_sid; helper/voice.js now passes callSid. Prevents an
  older call's status update from clobbering the newest bubble.
This commit is contained in:
Muhsin
2026-04-17 22:28:10 +04:00
parent 07de9ae1f1
commit 12dc22b693
8 changed files with 45 additions and 53 deletions
+1
View File
@@ -63,6 +63,7 @@ export function handleVoiceCallUpdated(commit, message, currentUserId) {
commit(types.UPDATE_MESSAGE_CALL_STATUS, {
conversationId,
callStatus: status,
callSid,
});
const isNewCall =
@@ -307,19 +307,23 @@ export const mutations = {
}
},
[types.UPDATE_MESSAGE_CALL_STATUS](_state, { conversationId, callStatus }) {
[types.UPDATE_MESSAGE_CALL_STATUS](
_state,
{ conversationId, callStatus, callSid }
) {
const chat = getConversationById(_state)(conversationId);
if (!chat) return;
const lastCall = (chat.messages || []).findLast(
m => m.content_type === CONTENT_TYPES.VOICE_CALL
const message = (chat.messages || []).find(
m =>
m.content_type === CONTENT_TYPES.VOICE_CALL &&
m.content_attributes?.data?.call_sid === callSid
);
if (!message) return;
if (!lastCall) return;
lastCall.content_attributes ??= {};
lastCall.content_attributes.data = {
...lastCall.content_attributes.data,
message.content_attributes ??= {};
message.content_attributes.data = {
...message.content_attributes.data,
status: callStatus,
};
},
@@ -8,11 +8,12 @@ describe('#mutations', () => {
mutations[types.UPDATE_MESSAGE_CALL_STATUS](state, {
conversationId: 1,
callStatus: 'ringing',
callSid: 'CA123',
});
expect(state.allConversations).toEqual([]);
});
it('does nothing if no voice call message exists', () => {
it('does nothing if no matching voice call message exists', () => {
const state = {
allConversations: [
{ id: 1, messages: [{ id: 1, content_type: 'text' }] },
@@ -21,6 +22,7 @@ describe('#mutations', () => {
mutations[types.UPDATE_MESSAGE_CALL_STATUS](state, {
conversationId: 1,
callStatus: 'ringing',
callSid: 'CA123',
});
expect(state.allConversations[0].messages[0]).toEqual({
id: 1,
@@ -28,7 +30,7 @@ describe('#mutations', () => {
});
});
it('updates the last voice call message status', () => {
it('updates only the voice call message matching the given callSid', () => {
const state = {
allConversations: [
{
@@ -37,12 +39,16 @@ describe('#mutations', () => {
{
id: 1,
content_type: 'voice_call',
content_attributes: { data: { status: 'ringing' } },
content_attributes: {
data: { call_sid: 'CA111', status: 'ringing' },
},
},
{
id: 2,
content_type: 'voice_call',
content_attributes: { data: { status: 'ringing' } },
content_attributes: {
data: { call_sid: 'CA222', status: 'ringing' },
},
},
],
},
@@ -51,31 +57,14 @@ describe('#mutations', () => {
mutations[types.UPDATE_MESSAGE_CALL_STATUS](state, {
conversationId: 1,
callStatus: 'in-progress',
callSid: 'CA111',
});
expect(
state.allConversations[0].messages[0].content_attributes.data.status
).toBe('ringing');
).toBe('in-progress');
expect(
state.allConversations[0].messages[1].content_attributes.data.status
).toBe('in-progress');
});
it('creates content_attributes.data if it does not exist', () => {
const state = {
allConversations: [
{
id: 1,
messages: [{ id: 1, content_type: 'voice_call' }],
},
],
};
mutations[types.UPDATE_MESSAGE_CALL_STATUS](state, {
conversationId: 1,
callStatus: 'completed',
});
expect(
state.allConversations[0].messages[0].content_attributes.data.status
).toBe('completed');
).toBe('ringing');
});
it('preserves existing data in content_attributes.data', () => {
@@ -98,6 +87,7 @@ describe('#mutations', () => {
mutations[types.UPDATE_MESSAGE_CALL_STATUS](state, {
conversationId: 1,
callStatus: 'in-progress',
callSid: 'CA123',
});
expect(
state.allConversations[0].messages[0].content_attributes.data
@@ -114,6 +104,7 @@ describe('#mutations', () => {
mutations[types.UPDATE_MESSAGE_CALL_STATUS](state, {
conversationId: 1,
callStatus: 'ringing',
callSid: 'CA123',
});
expect(state.allConversations[0].messages).toEqual([]);
});