fix: stop the no-answer retry from racing the dialplan hangup + fix event catalog validation

Two bugs found via a live test with two real registered browser softphones:

1. retry_escalation_no_answer() re-resolved the client channel via
   _resolve_handoff_channel() -> a live AMI CoreShowChannels round-trip that
   can take ~10s. The new mvpcc-transfer dialplan wait window (MusicOnHold,
   also ~10s, added to give the backend time to redirect before the final
   Hangup) was consistently LOST to this exact same duration: the backend's
   AMI Redirect fired against a channel the dialplan had already hung up
   ('Channel does not exist: PJSIP/...', confirmed in escalation timeline).
   Fixed by reusing the actively-maintained AsteriskCallLinkRow.channel_name
   directly (unchanged for a PJSIP channel across Redirect between contexts
   of the same call) instead of re-discovering it, falling back to the slow
   path only if that field is empty.

2. VoiceEventIn.event_type is a pydantic Literal restricted to 6 legacy
   values (call.started/ivr.completed/...). None of the Phase 2 event
   catalog names (AgentReserved/AgentRinging/AgentNoAnswer/AgentConnected/
   TransferCompleted/TransferFailed) were ever in it, so every single
   _emit_escalation_event() call has been failing with 422 since Phase 2
   shipped (swallowed silently by the broad except there) - confirmed by
   calling app._emit_voice_event() directly against the running service.
   Extended the Literal to include all six.
This commit is contained in:
arys
2026-08-31 01:03:47 +05:00
parent 9fdaa9472f
commit 99d169ec67
2 changed files with 11 additions and 1 deletions
+5 -1
View File
@@ -938,7 +938,11 @@ def retry_escalation_no_answer(session, *, call_id: str, dial_outcome: str) -> N
) )
attempted_ids = json.loads(escalation.attempted_agent_ids_json or "[]") attempted_ids = json.loads(escalation.attempted_agent_ids_json or "[]")
channel = _resolve_handoff_channel(session, link) # Use the already-known, actively-maintained channel name directly instead of
# _resolve_handoff_channel()'s live AMI CoreShowChannels re-discovery: that
# round-trip can take ~10s, which races (and loses) against the dialplan's
# own short MusicOnHold-then-hangup wait window for this exact retry path.
channel = str(link.channel_name or "").strip() or _resolve_handoff_channel(session, link)
required_skills = json.loads(escalation.required_skills_json or "[]") required_skills = json.loads(escalation.required_skills_json or "[]")
next_agent = _reserve_routing_agent( next_agent = _reserve_routing_agent(
call_id=call_id, call_id=call_id,
+6
View File
@@ -363,6 +363,12 @@ class VoiceEventIn(BaseModel):
"recording.ready", "recording.ready",
"call.connected", "call.connected",
"call.transferred", "call.transferred",
"AgentReserved",
"AgentRinging",
"AgentNoAnswer",
"AgentConnected",
"TransferCompleted",
"TransferFailed",
] ]
call_id: str call_id: str
interaction_id: str | None = None interaction_id: str | None = None