fix: SIP driven by Agent entity, token refresh, network indicator

- SIP connection only for users with Agent entity (no env var fallback)
- Supervisor no longer intercepts CC agent calls
- Auth controller checks Agent entity for ALL roles, not just cc-agent
- Token refresh handles GraphQL UNAUTHENTICATED errors (200 with error body)
- Token refresh handles sidecar 400s from expired upstream tokens
- Network quality indicator in sidebar (offline/unstable/good)
- Ozonetel IDLE event mapped to ready state (fixes stuck calling after canceled call)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-25 11:51:32 +05:30
parent 70e0f6fc3e
commit daa2fbb0c2
4 changed files with 121 additions and 19 deletions

View File

@@ -14,27 +14,25 @@ import { registerSipStateUpdater, connectSip, disconnectSip, getSipClient, setOu
import { apiClient } from '@/lib/api-client';
import type { SIPConfig } from '@/types/sip';
const getSipConfig = (): SIPConfig => {
// SIP config comes exclusively from the Agent entity (stored on login).
// No env var fallback — users without an Agent entity don't connect SIP.
const getSipConfig = (): SIPConfig | null => {
try {
const stored = localStorage.getItem('helix_agent_config');
if (stored) {
const config = JSON.parse(stored);
return {
displayName: 'Helix Agent',
uri: config.sipUri,
password: config.sipPassword,
wsServer: config.sipWsServer,
stunServers: 'stun:stun.l.google.com:19302',
};
if (config.sipUri && config.sipWsServer) {
return {
displayName: 'Helix Agent',
uri: config.sipUri,
password: config.sipPassword,
wsServer: config.sipWsServer,
stunServers: 'stun:stun.l.google.com:19302',
};
}
}
} catch {}
return {
displayName: import.meta.env.VITE_SIP_DISPLAY_NAME ?? 'Helix Agent',
uri: import.meta.env.VITE_SIP_URI ?? '',
password: import.meta.env.VITE_SIP_PASSWORD ?? '',
wsServer: import.meta.env.VITE_SIP_WS_SERVER ?? '',
stunServers: 'stun:stun.l.google.com:19302',
};
return null;
};
export const SipProvider = ({ children }: PropsWithChildren) => {
@@ -55,9 +53,14 @@ export const SipProvider = ({ children }: PropsWithChildren) => {
});
}, [setConnectionStatus, setCallState, setCallerNumber, setCallUcid]);
// Auto-connect SIP on mount
// Auto-connect SIP on mount — only if Agent entity has SIP config
useEffect(() => {
connectSip(getSipConfig());
const config = getSipConfig();
if (config) {
connectSip(config);
} else {
console.log('[SIP] No agent SIP config — skipping connection');
}
}, []);
// Call duration timer
@@ -178,7 +181,7 @@ export const useSip = () => {
isInCall: ['ringing-in', 'ringing-out', 'active'].includes(callState),
ozonetelStatus: 'logged-in' as const,
ozonetelError: null as string | null,
connect: () => connectSip(getSipConfig()),
connect: () => { const c = getSipConfig(); if (c) connectSip(c); },
disconnect: disconnectSip,
makeCall,
dialOutbound,