@gravity-rail/sdk 0.22.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -8
- package/dist/agui.d.cts +1 -1
- package/dist/agui.d.ts +1 -1
- package/dist/audio.d.cts +1 -1
- package/dist/audio.d.ts +1 -1
- package/dist/{client-C10qSd6b.d.ts → chat-interaction-BKdImgZ5.d.ts} +5604 -1531
- package/dist/{client-Ct0soeip.d.cts → chat-interaction-DLfC_h_P.d.cts} +5604 -1531
- package/dist/{chunk-3HAV5XUJ.js → chunk-HRUMZ46V.js} +1 -1
- package/dist/chunk-HRUMZ46V.js.map +1 -0
- package/dist/{chunk-WMA6YCAS.js → chunk-N446FGCT.js} +1230 -201
- package/dist/chunk-N446FGCT.js.map +1 -0
- package/dist/{chunk-7OLBLLV7.js → chunk-P2E6LU7I.js} +57 -7
- package/dist/chunk-P2E6LU7I.js.map +1 -0
- package/dist/helpers.cjs +54 -1
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +3 -2
- package/dist/helpers.d.ts +3 -2
- package/dist/helpers.js +11 -3
- package/dist/helpers.js.map +1 -1
- package/dist/index.cjs +2291 -148
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +509 -6
- package/dist/index.d.ts +509 -6
- package/dist/index.js +1032 -23
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +1320 -220
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +33 -49
- package/dist/internal.d.ts +33 -49
- package/dist/internal.js +73 -33
- package/dist/internal.js.map +1 -1
- package/dist/realtime.cjs +279 -14
- package/dist/realtime.cjs.map +1 -1
- package/dist/realtime.d.cts +169 -17
- package/dist/realtime.d.ts +169 -17
- package/dist/realtime.js +264 -13
- package/dist/realtime.js.map +1 -1
- package/dist/{revision-state-CCPTiW-i.d.cts → revision-state-Bsq5dKwi.d.cts} +236 -5
- package/dist/{revision-state-C6aM-PHu.d.ts → revision-state-CQTYcIRG.d.ts} +236 -5
- package/dist/schemas.cjs +3 -1
- package/dist/schemas.cjs.map +1 -1
- package/dist/schemas.d.cts +31 -13
- package/dist/schemas.d.ts +31 -13
- package/dist/schemas.js +4 -2
- package/dist/schemas.js.map +1 -1
- package/dist/{tasks-WH_VfzMv.d.cts → tasks-B-31JPuq.d.cts} +298 -114
- package/dist/{tasks-WH_VfzMv.d.ts → tasks-B-31JPuq.d.ts} +298 -114
- package/dist/types-0c6_NGU4.d.cts +90 -0
- package/dist/types-0c6_NGU4.d.ts +90 -0
- package/package.json +3 -3
- package/dist/chunk-3HAV5XUJ.js.map +0 -1
- package/dist/chunk-7OLBLLV7.js.map +0 -1
- package/dist/chunk-WMA6YCAS.js.map +0 -1
- package/dist/types-DkYyrvoC.d.cts +0 -19
- package/dist/types-DkYyrvoC.d.ts +0 -19
package/dist/realtime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/realtime/types.ts","../src/realtime/workspace-client.ts","../src/realtime/operator-session-client.ts"],"sourcesContent":["/** Event received from a subscribed workspace realtime channel. */\nexport interface ChannelEvent {\n type: string;\n [key: string]: unknown;\n}\n\nexport type ChannelListener = (event: ChannelEvent) => void;\nexport type Unsubscribe = () => void;\n\n/** Auth context for workspace realtime WebSocket connections. */\nexport interface RealtimeAuthSession {\n apiUrl: string;\n workspaceUuid: string;\n accessToken: string;\n}\n\nexport type OperatorSessionState = 'idle' | 'connecting' | 'connected' | 'error';\n\nexport type OperatorCallPhase =\n | 'idle'\n | 'connecting'\n | 'ready'\n | 'dialing'\n | 'live'\n | 'onHold'\n | 'ended'\n | 'failed';\n\n/** Phases where Hold / Forward / Hang up apply to an active phone leg. */\nexport function isActiveCallPhase(phase: OperatorCallPhase): boolean {\n return phase === 'dialing' || phase === 'live' || phase === 'onHold';\n}\n","/**\n * Unified workspace WebSocket client for real-time events.\n *\n * Mirrors `/api/v2/w/{wid}/ws/realtime`: login with OAuth access token,\n * subscribe to channels, receive AGUI events.\n *\n * Protocol:\n * Client → Server: subscribe, unsubscribe, heartbeat, message\n * Server → Client: protocol.init, subscribed, unsubscribed, event, error\n */\n\nimport type { ChannelEvent, ChannelListener, RealtimeAuthSession, Unsubscribe } from './types';\n\nconst HEARTBEAT_INTERVAL_MS = 30_000;\nconst RECONNECT_DELAY_MS = 3_000;\nconst MAX_RECONNECT_ATTEMPTS = 5;\nconst CLOSE_DELAY_MS = 1_000;\n\ninterface SocketState {\n ws: WebSocket | null;\n connected: boolean;\n connecting: boolean;\n listeners: Map<string, Set<ChannelListener>>;\n subscribedChannels: Set<string>;\n reconnectAttempts: number;\n reconnectTimer: ReturnType<typeof setTimeout> | null;\n heartbeatTimer: ReturnType<typeof setInterval> | null;\n closeTimer: ReturnType<typeof setTimeout> | null;\n intentionalClose: boolean;\n}\n\nfunction createSocketState(): SocketState {\n return {\n ws: null,\n connected: false,\n connecting: false,\n listeners: new Map(),\n subscribedChannels: new Set(),\n reconnectAttempts: 0,\n reconnectTimer: null,\n heartbeatTimer: null,\n closeTimer: null,\n intentionalClose: false,\n };\n}\n\n/** Build the workspace realtime WebSocket URL from session config. */\nexport function buildWorkspaceWsUrl(session: RealtimeAuthSession): string {\n const isSsl = session.apiUrl.startsWith('https');\n const host = session.apiUrl.replace(/^https?:\\/\\//, '');\n return `${isSsl ? 'wss' : 'ws'}://${host}/api/v2/w/${session.workspaceUuid}/ws/realtime`;\n}\n\nexport class WorkspaceRealtimeClient {\n private session: RealtimeAuthSession | null = null;\n private readonly getSessionFn: (() => RealtimeAuthSession | null) | null;\n private readonly state: SocketState = createSocketState();\n private readonly connectionListeners = new Set<(connected: boolean) => void>();\n\n constructor(config?: RealtimeAuthSession | (() => RealtimeAuthSession | null)) {\n if (typeof config === 'function') {\n this.getSessionFn = config;\n } else {\n this.getSessionFn = null;\n if (config) {\n this.session = config;\n }\n }\n }\n\n private resolveSession(): RealtimeAuthSession | null {\n return this.session ?? this.getSessionFn?.() ?? null;\n }\n\n private notifyConnectionListeners(): void {\n for (const listener of this.connectionListeners) {\n listener(this.state.connected);\n }\n }\n\n private clearCloseTimer(): void {\n if (this.state.closeTimer) {\n clearTimeout(this.state.closeTimer);\n this.state.closeTimer = null;\n }\n }\n\n setSession(session: RealtimeAuthSession): void {\n const changed =\n this.session?.accessToken !== session.accessToken ||\n this.session?.workspaceUuid !== session.workspaceUuid ||\n this.session?.apiUrl !== session.apiUrl;\n\n this.session = session;\n if (changed && this.state.ws) {\n this.closeConnection();\n }\n if (this.state.listeners.size > 0) {\n this.ensureConnection();\n }\n }\n\n subscribe(channel: string, listener: ChannelListener): Unsubscribe {\n this.clearCloseTimer();\n\n let listeners = this.state.listeners.get(channel);\n if (!listeners) {\n listeners = new Set();\n this.state.listeners.set(channel, listeners);\n }\n listeners.add(listener);\n\n this.ensureConnection();\n\n if (\n this.state.connected &&\n this.state.ws?.readyState === WebSocket.OPEN &&\n !this.state.subscribedChannels.has(channel)\n ) {\n this.state.ws.send(JSON.stringify({ type: 'subscribe', channels: [channel] }));\n }\n\n return () => {\n const channelListeners = this.state.listeners.get(channel);\n channelListeners?.delete(listener);\n if (channelListeners && channelListeners.size === 0) {\n this.state.listeners.delete(channel);\n this.state.subscribedChannels.delete(channel);\n if (this.state.connected && this.state.ws?.readyState === WebSocket.OPEN) {\n this.state.ws.send(JSON.stringify({ type: 'unsubscribe', channels: [channel] }));\n }\n }\n\n if (this.state.listeners.size === 0) {\n this.state.closeTimer = setTimeout(() => {\n if (this.state.listeners.size === 0) {\n this.closeConnection();\n }\n }, CLOSE_DELAY_MS);\n }\n };\n }\n\n send(channel: string, data: unknown): void {\n if (!this.state.connected || !this.state.ws) return;\n this.state.ws.send(JSON.stringify({ type: 'message', channel, data }));\n }\n\n onConnection(listener: (connected: boolean) => void): () => void {\n this.connectionListeners.add(listener);\n listener(this.state.connected);\n return () => {\n this.connectionListeners.delete(listener);\n };\n }\n\n isConnected(): boolean {\n return this.state.connected;\n }\n\n private ensureConnection(): void {\n const session = this.resolveSession();\n if (!session) return;\n if (typeof window === 'undefined' || typeof WebSocket === 'undefined') return;\n if (this.state.ws || this.state.connecting) return;\n\n this.clearCloseTimer();\n this.state.connecting = true;\n this.state.intentionalClose = false;\n\n const ws = new WebSocket(buildWorkspaceWsUrl(session));\n\n ws.onopen = () => {\n ws.send(JSON.stringify({ type: 'login', token: session.accessToken }));\n };\n\n ws.onmessage = (event: MessageEvent) => {\n try {\n const msg = JSON.parse(String(event.data)) as {\n type?: string;\n channels?: string[];\n channel?: string;\n event?: ChannelEvent;\n };\n\n if (msg.type === 'protocol.init' && !this.state.connected) {\n this.state.connected = true;\n this.state.connecting = false;\n this.state.reconnectAttempts = 0;\n\n if (this.state.heartbeatTimer) clearInterval(this.state.heartbeatTimer);\n this.state.heartbeatTimer = setInterval(() => {\n if (this.state.ws?.readyState === WebSocket.OPEN) {\n this.state.ws.send(JSON.stringify({ type: 'heartbeat' }));\n }\n }, HEARTBEAT_INTERVAL_MS);\n\n if (this.state.listeners.size > 0) {\n ws.send(\n JSON.stringify({\n type: 'subscribe',\n channels: Array.from(this.state.listeners.keys()),\n })\n );\n }\n\n this.notifyConnectionListeners();\n return;\n }\n\n if (msg.type === 'subscribed' && Array.isArray(msg.channels)) {\n for (const ch of msg.channels) {\n this.state.subscribedChannels.add(ch);\n }\n return;\n }\n\n if (msg.type === 'unsubscribed' && Array.isArray(msg.channels)) {\n for (const ch of msg.channels) {\n this.state.subscribedChannels.delete(ch);\n }\n return;\n }\n\n if (msg.type === 'event' && msg.channel && msg.event) {\n const channelListeners = this.state.listeners.get(msg.channel);\n if (channelListeners) {\n for (const channelListener of channelListeners) {\n try {\n channelListener(msg.event);\n } catch (err) {\n console.error('Channel listener error:', err);\n }\n }\n }\n }\n } catch {\n // Ignore malformed frames.\n }\n };\n\n ws.onclose = () => {\n this.state.connected = false;\n this.state.connecting = false;\n this.state.ws = null;\n this.state.subscribedChannels.clear();\n\n if (this.state.heartbeatTimer) {\n clearInterval(this.state.heartbeatTimer);\n this.state.heartbeatTimer = null;\n }\n\n this.notifyConnectionListeners();\n\n if (\n !this.state.intentionalClose &&\n this.state.listeners.size > 0 &&\n this.state.reconnectAttempts < MAX_RECONNECT_ATTEMPTS\n ) {\n this.state.reconnectAttempts += 1;\n this.state.reconnectTimer = setTimeout(() => {\n this.ensureConnection();\n }, RECONNECT_DELAY_MS * this.state.reconnectAttempts);\n }\n };\n\n ws.onerror = () => {\n // onclose will fire after this\n };\n\n this.state.ws = ws;\n }\n\n private closeConnection(): void {\n this.state.intentionalClose = true;\n this.clearCloseTimer();\n if (this.state.reconnectTimer) {\n clearTimeout(this.state.reconnectTimer);\n this.state.reconnectTimer = null;\n }\n if (this.state.heartbeatTimer) {\n clearInterval(this.state.heartbeatTimer);\n this.state.heartbeatTimer = null;\n }\n this.state.ws?.close();\n this.state.ws = null;\n this.state.connected = false;\n this.state.connecting = false;\n this.state.subscribedChannels.clear();\n this.notifyConnectionListeners();\n }\n}\n\nconst defaultClient = new WorkspaceRealtimeClient();\n\nexport function initWorkspaceSocket(session: RealtimeAuthSession): void {\n defaultClient.setSession(session);\n}\n\nexport function subscribeWorkspaceChannel(channel: string, listener: ChannelListener): Unsubscribe {\n return defaultClient.subscribe(channel, listener);\n}\n\nexport function onWorkspaceSocketConnection(listener: (connected: boolean) => void): () => void {\n return defaultClient.onConnection(listener);\n}\n\nexport function isWorkspaceSocketConnected(): boolean {\n return defaultClient.isConnected();\n}\n","/**\n * Human operator session WebSocket client.\n *\n * Connects to `/api/v2/w/{wid}/operator/chats/{chatId}/session-ws` — AGUI\n * events, bidirectional PCM, and call control. No AI inference.\n */\n\nimport {\n base64ToFloat32Array,\n float32ArrayToBase64,\n SERVER_SAMPLE_RATE,\n TARGET_SAMPLE_RATE,\n} from '../audio/pcm';\nimport type {\n ChannelEvent,\n OperatorCallPhase,\n OperatorSessionState,\n RealtimeAuthSession,\n} from './types';\n\nexport interface OperatorSessionHandlers {\n onSessionStateChange?: (state: OperatorSessionState) => void;\n onCallPhaseChange?: (phase: OperatorCallPhase) => void;\n onAguiEvent?: (event: ChannelEvent) => void;\n onError?: (message: string) => void;\n /** Fired when the server closes the session (e.g. callee hung up). */\n onSessionClosed?: (reason?: string) => void;\n}\n\nexport class OperatorSessionClient {\n private ws: WebSocket | null = null;\n private audioContext: AudioContext | null = null;\n private mediaStream: MediaStream | null = null;\n private processor: ScriptProcessorNode | null = null;\n private sessionState: OperatorSessionState = 'idle';\n private callPhase: OperatorCallPhase = 'idle';\n private nextPlaybackTime = 0;\n private playbackSources: AudioBufferSourceNode[] = [];\n private micActive = false;\n private onHold = false;\n private muted = false;\n private receivedRemoteAudio = false;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private sessionCloseNotified = false;\n private intentionalDisconnect = false;\n\n constructor(private handlers: OperatorSessionHandlers = {}) {}\n\n getSessionState(): OperatorSessionState {\n return this.sessionState;\n }\n\n getCallPhase(): OperatorCallPhase {\n return this.callPhase;\n }\n\n private setSessionState(next: OperatorSessionState): void {\n this.sessionState = next;\n this.handlers.onSessionStateChange?.(next);\n }\n\n private setCallPhase(next: OperatorCallPhase): void {\n this.callPhase = next;\n this.handlers.onCallPhaseChange?.(next);\n }\n\n private notifySessionClosed(reason?: string): void {\n if (this.sessionCloseNotified) return;\n this.sessionCloseNotified = true;\n this.handlers.onSessionClosed?.(reason);\n }\n\n async connect(session: RealtimeAuthSession, chatId: number): Promise<void> {\n if (typeof window === 'undefined' || typeof WebSocket === 'undefined') {\n throw new Error('Operator session WebSocket is not available in this environment');\n }\n\n await this.disconnect();\n this.sessionCloseNotified = false;\n this.setSessionState('connecting');\n this.setCallPhase('connecting');\n\n const isSsl = session.apiUrl.startsWith('https');\n const host = session.apiUrl.replace(/^https?:\\/\\//, '');\n const wsUrl = `${isSsl ? 'wss' : 'ws'}://${host}/api/v2/w/${session.workspaceUuid}/operator/chats/${chatId}/session-ws`;\n\n await new Promise<void>((resolve, reject) => {\n const ws = new WebSocket(wsUrl);\n this.ws = ws;\n\n ws.onopen = () => {\n ws.send(JSON.stringify({ type: 'login', token: session.accessToken }));\n };\n\n ws.onmessage = (event) => {\n try {\n const msg = JSON.parse(String(event.data)) as {\n type?: string;\n name?: string;\n value?: Record<string, unknown>;\n message?: string;\n code?: string;\n event?: ChannelEvent;\n };\n this.handleMessage(msg, resolve, reject);\n } catch {\n /* ignore malformed frames */\n }\n };\n\n ws.onerror = () => {\n const message = 'Operator session WebSocket error';\n const wasConnecting = this.sessionState === 'connecting';\n this.setSessionState('error');\n if (wasConnecting) {\n this.setCallPhase('idle');\n reject(new Error(message));\n } else {\n this.setCallPhase('failed');\n this.handlers.onError?.(message);\n }\n };\n\n ws.onclose = () => {\n this.clearHeartbeat();\n this.stopMicrophone();\n this.stopPlayback();\n const wasConnected = this.sessionState === 'connected';\n this.setSessionState('idle');\n this.setCallPhase('idle');\n if (wasConnected && !this.intentionalDisconnect) {\n this.notifySessionClosed('disconnect');\n }\n this.intentionalDisconnect = false;\n };\n });\n }\n\n private handleMessage(\n msg: {\n type?: string;\n name?: string;\n value?: Record<string, unknown>;\n message?: string;\n code?: string;\n event?: ChannelEvent;\n },\n resolveLogin: () => void,\n rejectLogin: (err: Error) => void\n ): void {\n if (msg.type === 'RUN_ERROR' || msg.type === 'error') {\n const message = msg.message ?? 'Operator session error';\n if (this.sessionState === 'connecting') {\n this.setSessionState('error');\n rejectLogin(new Error(message));\n } else {\n this.setCallPhase('failed');\n this.handlers.onError?.(message);\n }\n return;\n }\n\n if (msg.type === 'protocol.init' && this.sessionState === 'connecting') {\n return;\n }\n\n if (msg.type === 'CUSTOM' && msg.name === 'operator.session_ready') {\n this.setSessionState('connected');\n this.setCallPhase('ready');\n this.startHeartbeat();\n resolveLogin();\n return;\n }\n\n if (msg.type === 'event' && msg.event) {\n if (this.handleRemoteVoiceAudio(msg.event)) {\n return;\n }\n this.handlers.onAguiEvent?.(msg.event);\n return;\n }\n\n if (msg.type === 'CUSTOM' && msg.name === 'operator.call.started') {\n this.receivedRemoteAudio = false;\n this.setCallPhase('dialing');\n return;\n }\n\n if (msg.type === 'CUSTOM' && msg.name === 'operator.call.ended') {\n this.onHold = false;\n this.muted = false;\n this.receivedRemoteAudio = false;\n this.stopMicrophone();\n this.stopPlayback();\n const endSession = msg.value?.end_session === true;\n const reason = typeof msg.value?.reason === 'string' ? msg.value.reason : undefined;\n if (endSession) {\n this.setCallPhase('idle');\n this.notifySessionClosed(reason ?? 'peer_hangup');\n } else {\n this.setCallPhase('ready');\n }\n return;\n }\n }\n\n /** Play callee PCM from wrapped chat-stream events; returns true if handled. */\n private handleRemoteVoiceAudio(event: ChannelEvent): boolean {\n if (event.type !== 'CUSTOM' || event.name !== 'voice.audio') {\n return false;\n }\n if (this.onHold) return true;\n\n const value = event.value;\n if (!value || typeof value !== 'object') return true;\n\n const data = (value as Record<string, unknown>).data;\n if (typeof data !== 'string' || !data) return true;\n\n if (\n !this.receivedRemoteAudio &&\n (this.callPhase === 'dialing' || this.callPhase === 'live')\n ) {\n this.receivedRemoteAudio = true;\n this.setCallPhase('live');\n }\n void this.playPcm16(base64ToFloat32Array(data));\n return true;\n }\n\n private startHeartbeat(): void {\n this.clearHeartbeat();\n this.heartbeatTimer = setInterval(() => {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify({ type: 'heartbeat' }));\n }\n }, 30_000);\n }\n\n private clearHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n\n private sendCustom(name: string, value: Record<string, unknown> = {}): void {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n throw new Error('Operator session WebSocket is not connected');\n }\n this.ws.send(JSON.stringify({ type: 'CUSTOM', name, value }));\n }\n\n async placeCall(memberId: number): Promise<void> {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n throw new Error('Operator session WebSocket is not connected');\n }\n if (this.sessionState !== 'connected') {\n throw new Error('Operator session is not ready');\n }\n await this.startMicrophone();\n this.sendCustom('operator.call.place', { member_id: memberId });\n }\n\n /**\n * Answer an inbound operator call the caller has already been routed to.\n *\n * Symmetric with {@link placeCall} but for the inbound direction: instead of\n * dialing an outbound leg it tells the backend to bridge the already-ringing\n * caller to this operator session. The backend (see\n * `lib/operator_session_handler.py::_answer_inbound_call`) registers the\n * audio bridge, marks the ring state `answered`, and replies with\n * `operator.call.started` — after which callee PCM arrives as `voice.audio`\n * events (played via {@link playPcm16}) and mic audio streams the other way,\n * exactly like an outbound call.\n *\n * Must be called once the session is ready (state `connected`). Starts the\n * microphone before sending so the operator's audio is flowing the instant\n * the bridge registers.\n */\n async answerCall(interactionId: string): Promise<void> {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n throw new Error('Operator session WebSocket is not connected');\n }\n if (this.sessionState !== 'connected') {\n throw new Error('Operator session is not ready');\n }\n await this.startMicrophone();\n this.sendCustom('operator.call.answer', { interaction_id: interactionId });\n }\n\n /**\n * Mute or unmute the operator's outbound microphone. Muting gates mic frames\n * locally (the caller stops hearing the operator) while callee audio keeps\n * playing — distinct from {@link setHold}, which is a two-way pause the\n * backend is told about. Purely client-side; no server round-trip.\n */\n setMuted(muted: boolean): void {\n this.muted = muted;\n }\n\n isMuted(): boolean {\n return this.muted;\n }\n\n hangUp(): void {\n try {\n this.sendCustom('operator.call.hangup');\n } catch {\n /* socket already closed */\n }\n this.onHold = false;\n this.receivedRemoteAudio = false;\n }\n\n forwardCall(phoneNumber: string): void {\n this.receivedRemoteAudio = false;\n this.setCallPhase('dialing');\n this.sendCustom('operator.call.forward', { phone_number: phoneNumber });\n }\n\n setHold(onHold: boolean): void {\n this.onHold = onHold;\n this.sendCustom('operator.call.hold', { on_hold: onHold });\n if (this.callPhase === 'live' || this.callPhase === 'onHold') {\n this.setCallPhase(onHold ? 'onHold' : 'live');\n }\n if (onHold) {\n this.stopPlayback();\n }\n }\n\n async startMicrophone(): Promise<void> {\n if (typeof window === 'undefined') return;\n if (!navigator.mediaDevices?.getUserMedia) {\n throw new Error('Microphone not available in this browser');\n }\n if (this.micActive) return;\n\n this.mediaStream = await navigator.mediaDevices.getUserMedia({\n audio: {\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n },\n });\n\n const Ctx =\n window.AudioContext ??\n (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Ctx) throw new Error('Web Audio not supported');\n\n this.audioContext = new Ctx();\n if (this.audioContext.state === 'suspended') {\n await this.audioContext.resume();\n }\n\n const source = this.audioContext.createMediaStreamSource(this.mediaStream);\n const processor = this.audioContext.createScriptProcessor(4096, 1, 1);\n this.processor = processor;\n\n processor.onaudioprocess = (event) => {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;\n if (this.onHold) return;\n if (this.muted) return;\n if (\n this.callPhase !== 'dialing' &&\n this.callPhase !== 'live' &&\n this.callPhase !== 'onHold'\n ) {\n return;\n }\n\n const input = event.inputBuffer.getChannelData(0);\n const copy = new Float32Array(input.length);\n copy.set(input);\n\n const sampleRate = this.audioContext?.sampleRate ?? TARGET_SAMPLE_RATE;\n let pcm = copy;\n if (sampleRate !== TARGET_SAMPLE_RATE) {\n const ratio = sampleRate / TARGET_SAMPLE_RATE;\n const newLen = Math.floor(copy.length / ratio);\n const down = new Float32Array(newLen);\n for (let i = 0; i < newLen; i += 1) {\n down[i] = copy[Math.floor(i * ratio)];\n }\n pcm = down;\n }\n\n this.ws.send(\n JSON.stringify({\n type: 'CUSTOM',\n name: 'voice.audio',\n value: {\n data: float32ArrayToBase64(pcm),\n format: 'pcm16',\n sample_rate: TARGET_SAMPLE_RATE,\n },\n })\n );\n };\n\n source.connect(processor);\n processor.connect(this.audioContext.destination);\n this.micActive = true;\n }\n\n stopMicrophone(): void {\n this.processor?.disconnect();\n this.processor = null;\n for (const track of this.mediaStream?.getTracks() ?? []) {\n track.stop();\n }\n this.mediaStream = null;\n this.micActive = false;\n }\n\n private async playPcm16(samples: Float32Array): Promise<void> {\n if (typeof window === 'undefined') return;\n\n const Ctx =\n window.AudioContext ??\n (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Ctx) return;\n\n if (!this.audioContext || this.audioContext.state === 'closed') {\n this.audioContext = new Ctx();\n }\n if (this.audioContext.state === 'suspended') {\n await this.audioContext.resume();\n }\n\n const buffer = this.audioContext.createBuffer(1, samples.length, SERVER_SAMPLE_RATE);\n buffer.copyToChannel(new Float32Array(samples), 0);\n\n const source = this.audioContext.createBufferSource();\n source.buffer = buffer;\n source.connect(this.audioContext.destination);\n\n const now = this.audioContext.currentTime;\n const startAt = this.nextPlaybackTime < now + 0.02 ? now + 0.02 : this.nextPlaybackTime;\n source.start(startAt);\n this.nextPlaybackTime = startAt + buffer.duration;\n this.playbackSources.push(source);\n source.onended = () => {\n const idx = this.playbackSources.indexOf(source);\n if (idx >= 0) this.playbackSources.splice(idx, 1);\n };\n }\n\n private stopPlayback(): void {\n for (const src of this.playbackSources) {\n try {\n src.stop();\n } catch {\n /* already stopped */\n }\n }\n this.playbackSources = [];\n this.nextPlaybackTime = 0;\n }\n\n async disconnect(): Promise<void> {\n this.intentionalDisconnect = true;\n if (\n this.callPhase === 'dialing' ||\n this.callPhase === 'live' ||\n this.callPhase === 'onHold'\n ) {\n this.hangUp();\n }\n this.clearHeartbeat();\n this.stopMicrophone();\n this.stopPlayback();\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n if (this.audioContext && this.audioContext.state !== 'closed') {\n await this.audioContext.close().catch(() => undefined);\n }\n this.audioContext = null;\n this.onHold = false;\n this.muted = false;\n this.receivedRemoteAudio = false;\n this.setSessionState('idle');\n this.setCallPhase('idle');\n }\n}\n"],"mappings":";;;;;;;;AA6BO,SAAS,kBAAkB,OAAmC;AAClE,SAAO,UAAU,aAAa,UAAU,UAAU,UAAU;AAC/D;;;AClBA,IAAM,wBAAwB;AAC9B,IAAM,qBAAqB;AAC3B,IAAM,yBAAyB;AAC/B,IAAM,iBAAiB;AAevB,SAAS,oBAAiC;AACvC,SAAO;AAAA,IACJ,IAAI;AAAA,IACJ,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,WAAW,oBAAI,IAAI;AAAA,IACnB,oBAAoB,oBAAI,IAAI;AAAA,IAC5B,mBAAmB;AAAA,IACnB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,YAAY;AAAA,IACZ,kBAAkB;AAAA,EACrB;AACH;AAGO,SAAS,oBAAoB,SAAsC;AACvE,QAAM,QAAQ,QAAQ,OAAO,WAAW,OAAO;AAC/C,QAAM,OAAO,QAAQ,OAAO,QAAQ,gBAAgB,EAAE;AACtD,SAAO,GAAG,QAAQ,QAAQ,IAAI,MAAM,IAAI,aAAa,QAAQ,aAAa;AAC7E;AAEO,IAAM,0BAAN,MAA8B;AAAA,EAC1B,UAAsC;AAAA,EAC7B;AAAA,EACA,QAAqB,kBAAkB;AAAA,EACvC,sBAAsB,oBAAI,IAAkC;AAAA,EAE7E,YAAY,QAAmE;AAC5E,QAAI,OAAO,WAAW,YAAY;AAC/B,WAAK,eAAe;AAAA,IACvB,OAAO;AACJ,WAAK,eAAe;AACpB,UAAI,QAAQ;AACT,aAAK,UAAU;AAAA,MAClB;AAAA,IACH;AAAA,EACH;AAAA,EAEQ,iBAA6C;AAClD,WAAO,KAAK,WAAW,KAAK,eAAe,KAAK;AAAA,EACnD;AAAA,EAEQ,4BAAkC;AACvC,eAAW,YAAY,KAAK,qBAAqB;AAC9C,eAAS,KAAK,MAAM,SAAS;AAAA,IAChC;AAAA,EACH;AAAA,EAEQ,kBAAwB;AAC7B,QAAI,KAAK,MAAM,YAAY;AACxB,mBAAa,KAAK,MAAM,UAAU;AAClC,WAAK,MAAM,aAAa;AAAA,IAC3B;AAAA,EACH;AAAA,EAEA,WAAW,SAAoC;AAC5C,UAAM,UACH,KAAK,SAAS,gBAAgB,QAAQ,eACtC,KAAK,SAAS,kBAAkB,QAAQ,iBACxC,KAAK,SAAS,WAAW,QAAQ;AAEpC,SAAK,UAAU;AACf,QAAI,WAAW,KAAK,MAAM,IAAI;AAC3B,WAAK,gBAAgB;AAAA,IACxB;AACA,QAAI,KAAK,MAAM,UAAU,OAAO,GAAG;AAChC,WAAK,iBAAiB;AAAA,IACzB;AAAA,EACH;AAAA,EAEA,UAAU,SAAiB,UAAwC;AAChE,SAAK,gBAAgB;AAErB,QAAI,YAAY,KAAK,MAAM,UAAU,IAAI,OAAO;AAChD,QAAI,CAAC,WAAW;AACb,kBAAY,oBAAI,IAAI;AACpB,WAAK,MAAM,UAAU,IAAI,SAAS,SAAS;AAAA,IAC9C;AACA,cAAU,IAAI,QAAQ;AAEtB,SAAK,iBAAiB;AAEtB,QACG,KAAK,MAAM,aACX,KAAK,MAAM,IAAI,eAAe,UAAU,QACxC,CAAC,KAAK,MAAM,mBAAmB,IAAI,OAAO,GAC3C;AACC,WAAK,MAAM,GAAG,KAAK,KAAK,UAAU,EAAE,MAAM,aAAa,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC;AAAA,IAChF;AAEA,WAAO,MAAM;AACV,YAAM,mBAAmB,KAAK,MAAM,UAAU,IAAI,OAAO;AACzD,wBAAkB,OAAO,QAAQ;AACjC,UAAI,oBAAoB,iBAAiB,SAAS,GAAG;AAClD,aAAK,MAAM,UAAU,OAAO,OAAO;AACnC,aAAK,MAAM,mBAAmB,OAAO,OAAO;AAC5C,YAAI,KAAK,MAAM,aAAa,KAAK,MAAM,IAAI,eAAe,UAAU,MAAM;AACvE,eAAK,MAAM,GAAG,KAAK,KAAK,UAAU,EAAE,MAAM,eAAe,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC;AAAA,QAClF;AAAA,MACH;AAEA,UAAI,KAAK,MAAM,UAAU,SAAS,GAAG;AAClC,aAAK,MAAM,aAAa,WAAW,MAAM;AACtC,cAAI,KAAK,MAAM,UAAU,SAAS,GAAG;AAClC,iBAAK,gBAAgB;AAAA,UACxB;AAAA,QACH,GAAG,cAAc;AAAA,MACpB;AAAA,IACH;AAAA,EACH;AAAA,EAEA,KAAK,SAAiB,MAAqB;AACxC,QAAI,CAAC,KAAK,MAAM,aAAa,CAAC,KAAK,MAAM,GAAI;AAC7C,SAAK,MAAM,GAAG,KAAK,KAAK,UAAU,EAAE,MAAM,WAAW,SAAS,KAAK,CAAC,CAAC;AAAA,EACxE;AAAA,EAEA,aAAa,UAAoD;AAC9D,SAAK,oBAAoB,IAAI,QAAQ;AACrC,aAAS,KAAK,MAAM,SAAS;AAC7B,WAAO,MAAM;AACV,WAAK,oBAAoB,OAAO,QAAQ;AAAA,IAC3C;AAAA,EACH;AAAA,EAEA,cAAuB;AACpB,WAAO,KAAK,MAAM;AAAA,EACrB;AAAA,EAEQ,mBAAyB;AAC9B,UAAM,UAAU,KAAK,eAAe;AACpC,QAAI,CAAC,QAAS;AACd,QAAI,OAAO,WAAW,eAAe,OAAO,cAAc,YAAa;AACvE,QAAI,KAAK,MAAM,MAAM,KAAK,MAAM,WAAY;AAE5C,SAAK,gBAAgB;AACrB,SAAK,MAAM,aAAa;AACxB,SAAK,MAAM,mBAAmB;AAE9B,UAAM,KAAK,IAAI,UAAU,oBAAoB,OAAO,CAAC;AAErD,OAAG,SAAS,MAAM;AACf,SAAG,KAAK,KAAK,UAAU,EAAE,MAAM,SAAS,OAAO,QAAQ,YAAY,CAAC,CAAC;AAAA,IACxE;AAEA,OAAG,YAAY,CAAC,UAAwB;AACrC,UAAI;AACD,cAAM,MAAM,KAAK,MAAM,OAAO,MAAM,IAAI,CAAC;AAOzC,YAAI,IAAI,SAAS,mBAAmB,CAAC,KAAK,MAAM,WAAW;AACxD,eAAK,MAAM,YAAY;AACvB,eAAK,MAAM,aAAa;AACxB,eAAK,MAAM,oBAAoB;AAE/B,cAAI,KAAK,MAAM,eAAgB,eAAc,KAAK,MAAM,cAAc;AACtE,eAAK,MAAM,iBAAiB,YAAY,MAAM;AAC3C,gBAAI,KAAK,MAAM,IAAI,eAAe,UAAU,MAAM;AAC/C,mBAAK,MAAM,GAAG,KAAK,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC,CAAC;AAAA,YAC3D;AAAA,UACH,GAAG,qBAAqB;AAExB,cAAI,KAAK,MAAM,UAAU,OAAO,GAAG;AAChC,eAAG;AAAA,cACA,KAAK,UAAU;AAAA,gBACZ,MAAM;AAAA,gBACN,UAAU,MAAM,KAAK,KAAK,MAAM,UAAU,KAAK,CAAC;AAAA,cACnD,CAAC;AAAA,YACJ;AAAA,UACH;AAEA,eAAK,0BAA0B;AAC/B;AAAA,QACH;AAEA,YAAI,IAAI,SAAS,gBAAgB,MAAM,QAAQ,IAAI,QAAQ,GAAG;AAC3D,qBAAW,MAAM,IAAI,UAAU;AAC5B,iBAAK,MAAM,mBAAmB,IAAI,EAAE;AAAA,UACvC;AACA;AAAA,QACH;AAEA,YAAI,IAAI,SAAS,kBAAkB,MAAM,QAAQ,IAAI,QAAQ,GAAG;AAC7D,qBAAW,MAAM,IAAI,UAAU;AAC5B,iBAAK,MAAM,mBAAmB,OAAO,EAAE;AAAA,UAC1C;AACA;AAAA,QACH;AAEA,YAAI,IAAI,SAAS,WAAW,IAAI,WAAW,IAAI,OAAO;AACnD,gBAAM,mBAAmB,KAAK,MAAM,UAAU,IAAI,IAAI,OAAO;AAC7D,cAAI,kBAAkB;AACnB,uBAAW,mBAAmB,kBAAkB;AAC7C,kBAAI;AACD,gCAAgB,IAAI,KAAK;AAAA,cAC5B,SAAS,KAAK;AACX,wBAAQ,MAAM,2BAA2B,GAAG;AAAA,cAC/C;AAAA,YACH;AAAA,UACH;AAAA,QACH;AAAA,MACH,QAAQ;AAAA,MAER;AAAA,IACH;AAEA,OAAG,UAAU,MAAM;AAChB,WAAK,MAAM,YAAY;AACvB,WAAK,MAAM,aAAa;AACxB,WAAK,MAAM,KAAK;AAChB,WAAK,MAAM,mBAAmB,MAAM;AAEpC,UAAI,KAAK,MAAM,gBAAgB;AAC5B,sBAAc,KAAK,MAAM,cAAc;AACvC,aAAK,MAAM,iBAAiB;AAAA,MAC/B;AAEA,WAAK,0BAA0B;AAE/B,UACG,CAAC,KAAK,MAAM,oBACZ,KAAK,MAAM,UAAU,OAAO,KAC5B,KAAK,MAAM,oBAAoB,wBAChC;AACC,aAAK,MAAM,qBAAqB;AAChC,aAAK,MAAM,iBAAiB,WAAW,MAAM;AAC1C,eAAK,iBAAiB;AAAA,QACzB,GAAG,qBAAqB,KAAK,MAAM,iBAAiB;AAAA,MACvD;AAAA,IACH;AAEA,OAAG,UAAU,MAAM;AAAA,IAEnB;AAEA,SAAK,MAAM,KAAK;AAAA,EACnB;AAAA,EAEQ,kBAAwB;AAC7B,SAAK,MAAM,mBAAmB;AAC9B,SAAK,gBAAgB;AACrB,QAAI,KAAK,MAAM,gBAAgB;AAC5B,mBAAa,KAAK,MAAM,cAAc;AACtC,WAAK,MAAM,iBAAiB;AAAA,IAC/B;AACA,QAAI,KAAK,MAAM,gBAAgB;AAC5B,oBAAc,KAAK,MAAM,cAAc;AACvC,WAAK,MAAM,iBAAiB;AAAA,IAC/B;AACA,SAAK,MAAM,IAAI,MAAM;AACrB,SAAK,MAAM,KAAK;AAChB,SAAK,MAAM,YAAY;AACvB,SAAK,MAAM,aAAa;AACxB,SAAK,MAAM,mBAAmB,MAAM;AACpC,SAAK,0BAA0B;AAAA,EAClC;AACH;AAEA,IAAM,gBAAgB,IAAI,wBAAwB;AAE3C,SAAS,oBAAoB,SAAoC;AACrE,gBAAc,WAAW,OAAO;AACnC;AAEO,SAAS,0BAA0B,SAAiB,UAAwC;AAChG,SAAO,cAAc,UAAU,SAAS,QAAQ;AACnD;AAEO,SAAS,4BAA4B,UAAoD;AAC7F,SAAO,cAAc,aAAa,QAAQ;AAC7C;AAEO,SAAS,6BAAsC;AACnD,SAAO,cAAc,YAAY;AACpC;;;ACxRO,IAAM,wBAAN,MAA4B;AAAA,EAiBhC,YAAoB,WAAoC,CAAC,GAAG;AAAxC;AAAA,EAAyC;AAAA,EAAzC;AAAA,EAhBZ,KAAuB;AAAA,EACvB,eAAoC;AAAA,EACpC,cAAkC;AAAA,EAClC,YAAwC;AAAA,EACxC,eAAqC;AAAA,EACrC,YAA+B;AAAA,EAC/B,mBAAmB;AAAA,EACnB,kBAA2C,CAAC;AAAA,EAC5C,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,sBAAsB;AAAA,EACtB,iBAAwD;AAAA,EACxD,uBAAuB;AAAA,EACvB,wBAAwB;AAAA,EAIhC,kBAAwC;AACrC,WAAO,KAAK;AAAA,EACf;AAAA,EAEA,eAAkC;AAC/B,WAAO,KAAK;AAAA,EACf;AAAA,EAEQ,gBAAgB,MAAkC;AACvD,SAAK,eAAe;AACpB,SAAK,SAAS,uBAAuB,IAAI;AAAA,EAC5C;AAAA,EAEQ,aAAa,MAA+B;AACjD,SAAK,YAAY;AACjB,SAAK,SAAS,oBAAoB,IAAI;AAAA,EACzC;AAAA,EAEQ,oBAAoB,QAAuB;AAChD,QAAI,KAAK,qBAAsB;AAC/B,SAAK,uBAAuB;AAC5B,SAAK,SAAS,kBAAkB,MAAM;AAAA,EACzC;AAAA,EAEA,MAAM,QAAQ,SAA8B,QAA+B;AACxE,QAAI,OAAO,WAAW,eAAe,OAAO,cAAc,aAAa;AACpE,YAAM,IAAI,MAAM,iEAAiE;AAAA,IACpF;AAEA,UAAM,KAAK,WAAW;AACtB,SAAK,uBAAuB;AAC5B,SAAK,gBAAgB,YAAY;AACjC,SAAK,aAAa,YAAY;AAE9B,UAAM,QAAQ,QAAQ,OAAO,WAAW,OAAO;AAC/C,UAAM,OAAO,QAAQ,OAAO,QAAQ,gBAAgB,EAAE;AACtD,UAAM,QAAQ,GAAG,QAAQ,QAAQ,IAAI,MAAM,IAAI,aAAa,QAAQ,aAAa,mBAAmB,MAAM;AAE1G,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC1C,YAAM,KAAK,IAAI,UAAU,KAAK;AAC9B,WAAK,KAAK;AAEV,SAAG,SAAS,MAAM;AACf,WAAG,KAAK,KAAK,UAAU,EAAE,MAAM,SAAS,OAAO,QAAQ,YAAY,CAAC,CAAC;AAAA,MACxE;AAEA,SAAG,YAAY,CAAC,UAAU;AACvB,YAAI;AACD,gBAAM,MAAM,KAAK,MAAM,OAAO,MAAM,IAAI,CAAC;AAQzC,eAAK,cAAc,KAAK,SAAS,MAAM;AAAA,QAC1C,QAAQ;AAAA,QAER;AAAA,MACH;AAEA,SAAG,UAAU,MAAM;AAChB,cAAM,UAAU;AAChB,cAAM,gBAAgB,KAAK,iBAAiB;AAC5C,aAAK,gBAAgB,OAAO;AAC5B,YAAI,eAAe;AAChB,eAAK,aAAa,MAAM;AACxB,iBAAO,IAAI,MAAM,OAAO,CAAC;AAAA,QAC5B,OAAO;AACJ,eAAK,aAAa,QAAQ;AAC1B,eAAK,SAAS,UAAU,OAAO;AAAA,QAClC;AAAA,MACH;AAEA,SAAG,UAAU,MAAM;AAChB,aAAK,eAAe;AACpB,aAAK,eAAe;AACpB,aAAK,aAAa;AAClB,cAAM,eAAe,KAAK,iBAAiB;AAC3C,aAAK,gBAAgB,MAAM;AAC3B,aAAK,aAAa,MAAM;AACxB,YAAI,gBAAgB,CAAC,KAAK,uBAAuB;AAC9C,eAAK,oBAAoB,YAAY;AAAA,QACxC;AACA,aAAK,wBAAwB;AAAA,MAChC;AAAA,IACH,CAAC;AAAA,EACJ;AAAA,EAEQ,cACL,KAQA,cACA,aACK;AACL,QAAI,IAAI,SAAS,eAAe,IAAI,SAAS,SAAS;AACnD,YAAM,UAAU,IAAI,WAAW;AAC/B,UAAI,KAAK,iBAAiB,cAAc;AACrC,aAAK,gBAAgB,OAAO;AAC5B,oBAAY,IAAI,MAAM,OAAO,CAAC;AAAA,MACjC,OAAO;AACJ,aAAK,aAAa,QAAQ;AAC1B,aAAK,SAAS,UAAU,OAAO;AAAA,MAClC;AACA;AAAA,IACH;AAEA,QAAI,IAAI,SAAS,mBAAmB,KAAK,iBAAiB,cAAc;AACrE;AAAA,IACH;AAEA,QAAI,IAAI,SAAS,YAAY,IAAI,SAAS,0BAA0B;AACjE,WAAK,gBAAgB,WAAW;AAChC,WAAK,aAAa,OAAO;AACzB,WAAK,eAAe;AACpB,mBAAa;AACb;AAAA,IACH;AAEA,QAAI,IAAI,SAAS,WAAW,IAAI,OAAO;AACpC,UAAI,KAAK,uBAAuB,IAAI,KAAK,GAAG;AACzC;AAAA,MACH;AACA,WAAK,SAAS,cAAc,IAAI,KAAK;AACrC;AAAA,IACH;AAEA,QAAI,IAAI,SAAS,YAAY,IAAI,SAAS,yBAAyB;AAChE,WAAK,sBAAsB;AAC3B,WAAK,aAAa,SAAS;AAC3B;AAAA,IACH;AAEA,QAAI,IAAI,SAAS,YAAY,IAAI,SAAS,uBAAuB;AAC9D,WAAK,SAAS;AACd,WAAK,QAAQ;AACb,WAAK,sBAAsB;AAC3B,WAAK,eAAe;AACpB,WAAK,aAAa;AAClB,YAAM,aAAa,IAAI,OAAO,gBAAgB;AAC9C,YAAM,SAAS,OAAO,IAAI,OAAO,WAAW,WAAW,IAAI,MAAM,SAAS;AAC1E,UAAI,YAAY;AACb,aAAK,aAAa,MAAM;AACxB,aAAK,oBAAoB,UAAU,aAAa;AAAA,MACnD,OAAO;AACJ,aAAK,aAAa,OAAO;AAAA,MAC5B;AACA;AAAA,IACH;AAAA,EACH;AAAA;AAAA,EAGQ,uBAAuB,OAA8B;AAC1D,QAAI,MAAM,SAAS,YAAY,MAAM,SAAS,eAAe;AAC1D,aAAO;AAAA,IACV;AACA,QAAI,KAAK,OAAQ,QAAO;AAExB,UAAM,QAAQ,MAAM;AACpB,QAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAEhD,UAAM,OAAQ,MAAkC;AAChD,QAAI,OAAO,SAAS,YAAY,CAAC,KAAM,QAAO;AAE9C,QACG,CAAC,KAAK,wBACL,KAAK,cAAc,aAAa,KAAK,cAAc,SACrD;AACC,WAAK,sBAAsB;AAC3B,WAAK,aAAa,MAAM;AAAA,IAC3B;AACA,SAAK,KAAK,UAAU,qBAAqB,IAAI,CAAC;AAC9C,WAAO;AAAA,EACV;AAAA,EAEQ,iBAAuB;AAC5B,SAAK,eAAe;AACpB,SAAK,iBAAiB,YAAY,MAAM;AACrC,UAAI,KAAK,IAAI,eAAe,UAAU,MAAM;AACzC,aAAK,GAAG,KAAK,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC,CAAC;AAAA,MACrD;AAAA,IACH,GAAG,GAAM;AAAA,EACZ;AAAA,EAEQ,iBAAuB;AAC5B,QAAI,KAAK,gBAAgB;AACtB,oBAAc,KAAK,cAAc;AACjC,WAAK,iBAAiB;AAAA,IACzB;AAAA,EACH;AAAA,EAEQ,WAAW,MAAc,QAAiC,CAAC,GAAS;AACzE,QAAI,CAAC,KAAK,MAAM,KAAK,GAAG,eAAe,UAAU,MAAM;AACpD,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAChE;AACA,SAAK,GAAG,KAAK,KAAK,UAAU,EAAE,MAAM,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA,EAC/D;AAAA,EAEA,MAAM,UAAU,UAAiC;AAC9C,QAAI,CAAC,KAAK,MAAM,KAAK,GAAG,eAAe,UAAU,MAAM;AACpD,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAChE;AACA,QAAI,KAAK,iBAAiB,aAAa;AACpC,YAAM,IAAI,MAAM,+BAA+B;AAAA,IAClD;AACA,UAAM,KAAK,gBAAgB;AAC3B,SAAK,WAAW,uBAAuB,EAAE,WAAW,SAAS,CAAC;AAAA,EACjE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,MAAM,WAAW,eAAsC;AACpD,QAAI,CAAC,KAAK,MAAM,KAAK,GAAG,eAAe,UAAU,MAAM;AACpD,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAChE;AACA,QAAI,KAAK,iBAAiB,aAAa;AACpC,YAAM,IAAI,MAAM,+BAA+B;AAAA,IAClD;AACA,UAAM,KAAK,gBAAgB;AAC3B,SAAK,WAAW,wBAAwB,EAAE,gBAAgB,cAAc,CAAC;AAAA,EAC5E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,SAAS,OAAsB;AAC5B,SAAK,QAAQ;AAAA,EAChB;AAAA,EAEA,UAAmB;AAChB,WAAO,KAAK;AAAA,EACf;AAAA,EAEA,SAAe;AACZ,QAAI;AACD,WAAK,WAAW,sBAAsB;AAAA,IACzC,QAAQ;AAAA,IAER;AACA,SAAK,SAAS;AACd,SAAK,sBAAsB;AAAA,EAC9B;AAAA,EAEA,YAAY,aAA2B;AACpC,SAAK,sBAAsB;AAC3B,SAAK,aAAa,SAAS;AAC3B,SAAK,WAAW,yBAAyB,EAAE,cAAc,YAAY,CAAC;AAAA,EACzE;AAAA,EAEA,QAAQ,QAAuB;AAC5B,SAAK,SAAS;AACd,SAAK,WAAW,sBAAsB,EAAE,SAAS,OAAO,CAAC;AACzD,QAAI,KAAK,cAAc,UAAU,KAAK,cAAc,UAAU;AAC3D,WAAK,aAAa,SAAS,WAAW,MAAM;AAAA,IAC/C;AACA,QAAI,QAAQ;AACT,WAAK,aAAa;AAAA,IACrB;AAAA,EACH;AAAA,EAEA,MAAM,kBAAiC;AACpC,QAAI,OAAO,WAAW,YAAa;AACnC,QAAI,CAAC,UAAU,cAAc,cAAc;AACxC,YAAM,IAAI,MAAM,0CAA0C;AAAA,IAC7D;AACA,QAAI,KAAK,UAAW;AAEpB,SAAK,cAAc,MAAM,UAAU,aAAa,aAAa;AAAA,MAC1D,OAAO;AAAA,QACJ,kBAAkB;AAAA,QAClB,kBAAkB;AAAA,QAClB,iBAAiB;AAAA,MACpB;AAAA,IACH,CAAC;AAED,UAAM,MACH,OAAO,gBACN,OAAmE;AACvE,QAAI,CAAC,IAAK,OAAM,IAAI,MAAM,yBAAyB;AAEnD,SAAK,eAAe,IAAI,IAAI;AAC5B,QAAI,KAAK,aAAa,UAAU,aAAa;AAC1C,YAAM,KAAK,aAAa,OAAO;AAAA,IAClC;AAEA,UAAM,SAAS,KAAK,aAAa,wBAAwB,KAAK,WAAW;AACzE,UAAM,YAAY,KAAK,aAAa,sBAAsB,MAAM,GAAG,CAAC;AACpE,SAAK,YAAY;AAEjB,cAAU,iBAAiB,CAAC,UAAU;AACnC,UAAI,CAAC,KAAK,MAAM,KAAK,GAAG,eAAe,UAAU,KAAM;AACvD,UAAI,KAAK,OAAQ;AACjB,UAAI,KAAK,MAAO;AAChB,UACG,KAAK,cAAc,aACnB,KAAK,cAAc,UACnB,KAAK,cAAc,UACpB;AACC;AAAA,MACH;AAEA,YAAM,QAAQ,MAAM,YAAY,eAAe,CAAC;AAChD,YAAM,OAAO,IAAI,aAAa,MAAM,MAAM;AAC1C,WAAK,IAAI,KAAK;AAEd,YAAM,aAAa,KAAK,cAAc,cAAc;AACpD,UAAI,MAAM;AACV,UAAI,eAAe,oBAAoB;AACpC,cAAM,QAAQ,aAAa;AAC3B,cAAM,SAAS,KAAK,MAAM,KAAK,SAAS,KAAK;AAC7C,cAAM,OAAO,IAAI,aAAa,MAAM;AACpC,iBAAS,IAAI,GAAG,IAAI,QAAQ,KAAK,GAAG;AACjC,eAAK,CAAC,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,CAAC;AAAA,QACvC;AACA,cAAM;AAAA,MACT;AAEA,WAAK,GAAG;AAAA,QACL,KAAK,UAAU;AAAA,UACZ,MAAM;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,YACJ,MAAM,qBAAqB,GAAG;AAAA,YAC9B,QAAQ;AAAA,YACR,aAAa;AAAA,UAChB;AAAA,QACH,CAAC;AAAA,MACJ;AAAA,IACH;AAEA,WAAO,QAAQ,SAAS;AACxB,cAAU,QAAQ,KAAK,aAAa,WAAW;AAC/C,SAAK,YAAY;AAAA,EACpB;AAAA,EAEA,iBAAuB;AACpB,SAAK,WAAW,WAAW;AAC3B,SAAK,YAAY;AACjB,eAAW,SAAS,KAAK,aAAa,UAAU,KAAK,CAAC,GAAG;AACtD,YAAM,KAAK;AAAA,IACd;AACA,SAAK,cAAc;AACnB,SAAK,YAAY;AAAA,EACpB;AAAA,EAEA,MAAc,UAAU,SAAsC;AAC3D,QAAI,OAAO,WAAW,YAAa;AAEnC,UAAM,MACH,OAAO,gBACN,OAAmE;AACvE,QAAI,CAAC,IAAK;AAEV,QAAI,CAAC,KAAK,gBAAgB,KAAK,aAAa,UAAU,UAAU;AAC7D,WAAK,eAAe,IAAI,IAAI;AAAA,IAC/B;AACA,QAAI,KAAK,aAAa,UAAU,aAAa;AAC1C,YAAM,KAAK,aAAa,OAAO;AAAA,IAClC;AAEA,UAAM,SAAS,KAAK,aAAa,aAAa,GAAG,QAAQ,QAAQ,kBAAkB;AACnF,WAAO,cAAc,IAAI,aAAa,OAAO,GAAG,CAAC;AAEjD,UAAM,SAAS,KAAK,aAAa,mBAAmB;AACpD,WAAO,SAAS;AAChB,WAAO,QAAQ,KAAK,aAAa,WAAW;AAE5C,UAAM,MAAM,KAAK,aAAa;AAC9B,UAAM,UAAU,KAAK,mBAAmB,MAAM,OAAO,MAAM,OAAO,KAAK;AACvE,WAAO,MAAM,OAAO;AACpB,SAAK,mBAAmB,UAAU,OAAO;AACzC,SAAK,gBAAgB,KAAK,MAAM;AAChC,WAAO,UAAU,MAAM;AACpB,YAAM,MAAM,KAAK,gBAAgB,QAAQ,MAAM;AAC/C,UAAI,OAAO,EAAG,MAAK,gBAAgB,OAAO,KAAK,CAAC;AAAA,IACnD;AAAA,EACH;AAAA,EAEQ,eAAqB;AAC1B,eAAW,OAAO,KAAK,iBAAiB;AACrC,UAAI;AACD,YAAI,KAAK;AAAA,MACZ,QAAQ;AAAA,MAER;AAAA,IACH;AACA,SAAK,kBAAkB,CAAC;AACxB,SAAK,mBAAmB;AAAA,EAC3B;AAAA,EAEA,MAAM,aAA4B;AAC/B,SAAK,wBAAwB;AAC7B,QACG,KAAK,cAAc,aACnB,KAAK,cAAc,UACnB,KAAK,cAAc,UACpB;AACC,WAAK,OAAO;AAAA,IACf;AACA,SAAK,eAAe;AACpB,SAAK,eAAe;AACpB,SAAK,aAAa;AAClB,QAAI,KAAK,IAAI;AACV,WAAK,GAAG,MAAM;AACd,WAAK,KAAK;AAAA,IACb;AACA,QAAI,KAAK,gBAAgB,KAAK,aAAa,UAAU,UAAU;AAC5D,YAAM,KAAK,aAAa,MAAM,EAAE,MAAM,MAAM,MAAS;AAAA,IACxD;AACA,SAAK,eAAe;AACpB,SAAK,SAAS;AACd,SAAK,QAAQ;AACb,SAAK,sBAAsB;AAC3B,SAAK,gBAAgB,MAAM;AAC3B,SAAK,aAAa,MAAM;AAAA,EAC3B;AACH;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/realtime/types.ts","../src/realtime/socket-client.ts","../src/realtime/workspace-client.ts","../src/realtime/account-client.ts","../src/realtime/operator-session-client.ts"],"sourcesContent":["/** Event received from a subscribed workspace realtime channel. */\nexport interface ChannelEvent {\n type: string;\n [key: string]: unknown;\n}\n\nexport type ChannelListener = (event: ChannelEvent) => void;\nexport type Unsubscribe = () => void;\n\n/**\n * What every realtime connection needs: where the API is, and who is asking.\n *\n * The account socket needs nothing more — its scope comes from the server's\n * grant map, not from the URL — so this is the base rather than a widening of\n * {@link RealtimeAuthSession}. A workspace session is assignable to it, which\n * is what lets one session object drive both clients during cutover.\n */\nexport interface RealtimeSessionBase {\n apiUrl: string;\n accessToken: string;\n}\n\n/** Auth context for workspace realtime WebSocket connections. */\nexport interface RealtimeAuthSession extends RealtimeSessionBase {\n workspaceUuid: string;\n}\n\n/** Auth context for the account realtime WebSocket. No workspace, by design. */\nexport type AccountRealtimeSession = RealtimeSessionBase;\n\nexport type OperatorSessionState = 'idle' | 'connecting' | 'connected' | 'error';\n\nexport type OperatorCallPhase =\n 'idle' | 'connecting' | 'ready' | 'dialing' | 'live' | 'onHold' | 'ended' | 'failed';\n\n/** Phases where Hold / Forward / Hang up apply to an active phone leg. */\nexport function isActiveCallPhase(phase: OperatorCallPhase): boolean {\n return phase === 'dialing' || phase === 'live' || phase === 'onHold';\n}\n\n// ---------------------------------------------------------------------------\n// Activity Spine (docs/projects/active/activity-spine.md)\n// ---------------------------------------------------------------------------\n\n/**\n * The two activity lanes a client can watch.\n *\n * `activity` is the whole workspace's feed; `activity:me` is the connected\n * member's own private lane and open items, kept separate so a busy workspace\n * does not wake every operator's badge on every event.\n *\n * Both are named constants rather than interpolated strings because the server\n * matches them exactly — a typo would subscribe successfully to nothing.\n */\nexport const ACTIVITY_CHANNEL = 'activity' as const;\nexport const ACTIVITY_MEMBER_CHANNEL = 'activity:me' as const;\n\n/**\n * The account's own lane, on the account socket only.\n *\n * Carries two things the workspace lanes cannot: the account's\n * `account_private` rows (manager / DM / agent-job chats, member-targeted\n * routine runs, and the concierge lane, which has no workspace at all), and a\n * change to the account's own grants.\n */\nexport const ACCOUNT_ACTIVITY_CHANNEL = 'account:activity' as const;\n\nexport type ActivityChannelName =\n typeof ACTIVITY_CHANNEL | typeof ACTIVITY_MEMBER_CHANNEL | typeof ACCOUNT_ACTIVITY_CHANNEL;\n\n/**\n * Operator presence, on the account socket only.\n *\n * The channel operator presence is **written** on. Presence state lives once,\n * at the account (`docs/projects/active/account-socket.md` § \"Presence,\n * migrated\"), and is projected into each chosen workspace's live set — so\n * going live is one command here rather than one per workspace socket.\n *\n * Unlike the activity lanes this one is bidirectional and does carry a\n * payload, because it is not an invalidation lane: `presence_confirmed`\n * reports the scope set the server actually honoured, which is the answer to\n * the command and is not re-fetchable from anywhere else.\n */\nexport const ACCOUNT_PRESENCE_CHANNEL = 'account:presence' as const;\n\n/** The workspace-scoped presence channel, on the per-workspace socket. */\nexport const PRESENCE_CHANNEL = 'presence' as const;\n\n/**\n * What an activity channel delivers — and deliberately all it delivers.\n *\n * **No event payloads travel over this socket.** The signal says *look again*;\n * the look goes through `POST /activity/query` (or `/counts` for a badge) under\n * the client's own session, where it is authorized against a freshly compiled\n * grant set and recorded as a bulk PHI access. A payload here would be an\n * authorization decision frozen at publish time by a producer that knows\n * nothing about who is listening — and a read that left no audit record.\n *\n * So: refetch on this, never render from it.\n */\nexport interface ActivityInvalidationEvent extends ChannelEvent {\n type: 'invalidate';\n data: {\n scope: 'workspace' | 'member' | 'account';\n reason: 'drain';\n };\n}\n\n/** Narrow a channel event to an activity invalidation. */\nexport function isActivityInvalidation(event: ChannelEvent): event is ActivityInvalidationEvent {\n if (event.type !== 'invalidate') return false;\n const data = (event as { data?: unknown }).data;\n if (typeof data !== 'object' || data === null) return false;\n const scope = (data as { scope?: unknown }).scope;\n return scope === 'workspace' || scope === 'member' || scope === 'account';\n}\n","/**\n * The realtime socket state machine, shared by both mounts.\n *\n * Two sockets speak the same protocol — `/api/v2/w/{wid}/ws/realtime` and\n * `/api/v2/ws/realtime` — and differ in exactly two places: the URL they open,\n * and the identity `protocol.init` hands back. Everything else (login,\n * heartbeat, reconnect with backoff, per-channel subscribe retry, listener\n * fan-out, deferred close) is one implementation here rather than two that\n * drift.\n *\n * That matters more than it sounds. The subscribe-retry and reconnect logic is\n * where the sharp edges live, and a second copy of it would be a second place\n * for a fix to be applied once.\n *\n * Protocol:\n * Client → Server: login, subscribe, unsubscribe, heartbeat, message\n * Server → Client: protocol.init, subscribed, subscribe_error, unsubscribed,\n * event, error\n */\n\nimport type { ChannelEvent, ChannelListener, RealtimeSessionBase, Unsubscribe } from './types';\n\nconst HEARTBEAT_INTERVAL_MS = 30_000;\nconst RECONNECT_DELAY_MS = 3_000;\nconst MAX_RECONNECT_ATTEMPTS = 5;\nconst CLOSE_DELAY_MS = 1_000;\nconst MAX_SUBSCRIBE_RETRY_DELAY_MS = 30_000;\n\ninterface SocketState {\n ws: WebSocket | null;\n connected: boolean;\n connecting: boolean;\n /**\n * The last transport-or-server failure, or null once a connection succeeds.\n *\n * The state machine has always known this; it simply had no way to say so,\n * so every consumer hardcoded `error: null` and reported a healthy socket\n * through an outage. Two things set it: a server `error` frame (which is\n * how a rejected login is reported), and running out of reconnect\n * attempts — the point at which the client has genuinely given up and the\n * user needs to know, rather than each transient close on the way there.\n */\n lastError: string | null;\n listeners: Map<string, Set<ChannelListener>>;\n subscribedChannels: Set<string>;\n subscriptionRevision: number;\n subscriptionRetryChannels: Set<string>;\n subscriptionRetryAttempts: number;\n subscriptionRetryTimer: ReturnType<typeof setTimeout> | null;\n reconnectAttempts: number;\n reconnectTimer: ReturnType<typeof setTimeout> | null;\n heartbeatTimer: ReturnType<typeof setInterval> | null;\n closeTimer: ReturnType<typeof setTimeout> | null;\n intentionalClose: boolean;\n}\n\nfunction createSocketState(): SocketState {\n return {\n ws: null,\n connected: false,\n connecting: false,\n lastError: null,\n listeners: new Map(),\n subscribedChannels: new Set(),\n subscriptionRevision: 0,\n subscriptionRetryChannels: new Set(),\n subscriptionRetryAttempts: 0,\n subscriptionRetryTimer: null,\n reconnectAttempts: 0,\n reconnectTimer: null,\n heartbeatTimer: null,\n closeTimer: null,\n intentionalClose: false,\n };\n}\n\n/** The `protocol.init` frame, before either subclass narrows it. */\nexport interface ProtocolInitMessage {\n memberId?: unknown;\n accountUuid?: unknown;\n workspaceUuids?: unknown;\n}\n\nexport abstract class RealtimeSocketClient<TSession extends RealtimeSessionBase> {\n private session: TSession | null = null;\n private readonly getSessionFn: (() => TSession | null) | null;\n private readonly state: SocketState = createSocketState();\n private readonly connectionListeners = new Set<(connected: boolean) => void>();\n\n constructor(config?: TSession | (() => TSession | null)) {\n if (typeof config === 'function') {\n this.getSessionFn = config;\n } else {\n this.getSessionFn = null;\n if (config) {\n this.session = config;\n }\n }\n }\n\n /** The WebSocket URL this mount connects to. */\n protected abstract buildUrl(session: TSession): string;\n\n /**\n * Record whatever identity `protocol.init` carried for this mount.\n *\n * Called before the connection is announced, so a `onConnection` listener\n * that immediately reads the identity always sees the new one.\n */\n protected abstract readProtocolInit(msg: ProtocolInitMessage): void;\n\n /** Forget the identity on a deliberate close. See {@link closeConnection}. */\n protected abstract clearIdentity(): void;\n\n /**\n * Whether replacing `previous` with `next` invalidates the open socket.\n *\n * The token always does; the rest is mount-specific — a workspace client\n * changing workspace is a different socket, and an account client has no\n * such axis.\n */\n protected sessionRequiresReconnect(previous: TSession | null, next: TSession): boolean {\n return previous?.accessToken !== next.accessToken || previous?.apiUrl !== next.apiUrl;\n }\n\n protected resolveSession(): TSession | null {\n return this.session ?? this.getSessionFn?.() ?? null;\n }\n\n private notifyConnectionListeners(): void {\n for (const listener of this.connectionListeners) {\n listener(this.state.connected);\n }\n }\n\n private clearCloseTimer(): void {\n if (this.state.closeTimer) {\n clearTimeout(this.state.closeTimer);\n this.state.closeTimer = null;\n }\n }\n\n private clearSubscriptionRetry(channel?: string): void {\n if (channel) {\n this.state.subscriptionRetryChannels.delete(channel);\n } else {\n this.state.subscriptionRetryChannels.clear();\n }\n\n if (this.state.subscriptionRetryChannels.size > 0) return;\n\n if (this.state.subscriptionRetryTimer) {\n clearTimeout(this.state.subscriptionRetryTimer);\n this.state.subscriptionRetryTimer = null;\n }\n this.state.subscriptionRetryAttempts = 0;\n }\n\n private scheduleSubscriptionRetry(channels: string[]): void {\n for (const channel of channels) {\n if (this.state.listeners.has(channel) && !this.state.subscribedChannels.has(channel)) {\n this.state.subscriptionRetryChannels.add(channel);\n }\n }\n\n if (this.state.subscriptionRetryChannels.size === 0 || this.state.subscriptionRetryTimer) {\n return;\n }\n\n this.state.subscriptionRetryAttempts += 1;\n const delay = Math.min(\n RECONNECT_DELAY_MS * this.state.subscriptionRetryAttempts,\n MAX_SUBSCRIBE_RETRY_DELAY_MS\n );\n\n this.state.subscriptionRetryTimer = setTimeout(() => {\n this.state.subscriptionRetryTimer = null;\n const retryChannels = Array.from(this.state.subscriptionRetryChannels).filter(\n (channel) =>\n this.state.listeners.has(channel) && !this.state.subscribedChannels.has(channel)\n );\n\n for (const channel of this.state.subscriptionRetryChannels) {\n if (!retryChannels.includes(channel)) {\n this.state.subscriptionRetryChannels.delete(channel);\n }\n }\n\n if (retryChannels.length === 0) {\n this.state.subscriptionRetryAttempts = 0;\n return;\n }\n\n if (this.state.connected && this.state.ws?.readyState === WebSocket.OPEN) {\n this.state.ws.send(JSON.stringify({ type: 'subscribe', channels: retryChannels }));\n }\n }, delay);\n }\n\n setSession(session: TSession): void {\n const changed = this.sessionRequiresReconnect(this.session, session);\n\n this.session = session;\n if (changed && this.state.ws) {\n this.closeConnection();\n }\n if (this.state.listeners.size > 0) {\n this.ensureConnection();\n }\n }\n\n subscribe(channel: string, listener: ChannelListener): Unsubscribe {\n this.clearCloseTimer();\n\n let listeners = this.state.listeners.get(channel);\n if (!listeners) {\n listeners = new Set();\n this.state.listeners.set(channel, listeners);\n }\n listeners.add(listener);\n\n this.ensureConnection();\n\n if (\n this.state.connected &&\n this.state.ws?.readyState === WebSocket.OPEN &&\n !this.state.subscribedChannels.has(channel)\n ) {\n this.state.ws.send(JSON.stringify({ type: 'subscribe', channels: [channel] }));\n }\n\n return () => {\n const channelListeners = this.state.listeners.get(channel);\n channelListeners?.delete(listener);\n if (channelListeners && channelListeners.size === 0) {\n this.state.listeners.delete(channel);\n this.state.subscribedChannels.delete(channel);\n this.clearSubscriptionRetry(channel);\n if (this.state.connected && this.state.ws?.readyState === WebSocket.OPEN) {\n this.state.ws.send(JSON.stringify({ type: 'unsubscribe', channels: [channel] }));\n }\n }\n\n if (this.state.listeners.size === 0) {\n this.state.closeTimer = setTimeout(() => {\n if (this.state.listeners.size === 0) {\n this.closeConnection();\n }\n }, CLOSE_DELAY_MS);\n }\n };\n }\n\n send(channel: string, data: unknown): void {\n if (!this.state.connected || !this.state.ws) return;\n this.state.ws.send(JSON.stringify({ type: 'message', channel, data }));\n }\n\n onConnection(listener: (connected: boolean) => void): () => void {\n this.connectionListeners.add(listener);\n listener(this.state.connected);\n return () => {\n this.connectionListeners.delete(listener);\n };\n }\n\n isConnected(): boolean {\n return this.state.connected;\n }\n\n /**\n * Whether the server has acknowledged this channel subscription.\n *\n * This is stricter than having a local listener: it becomes true only after\n * a `subscribed` frame, returns to false on disconnect or server-driven\n * unsubscribe, and stays false while automatic subscribe retry is pending.\n * Consumers that must order a command after subscription admission should\n * observe {@link getSubscriptionRevision} and re-read this method.\n */\n isSubscribed(channel: string): boolean {\n return this.state.subscribedChannels.has(channel);\n }\n\n /**\n * Monotonic local revision of acknowledged subscription state.\n *\n * It increments when `subscribed` or `unsubscribed` changes the server-\n * acknowledged set. It is not a protocol sequence number and resets with a\n * new client instance; its purpose is external-store invalidation before a\n * consumer calls {@link isSubscribed} again.\n */\n getSubscriptionRevision(): number {\n return this.state.subscriptionRevision;\n }\n\n /**\n * A socket is open or opening and has not yet said `protocol.init`.\n *\n * Real state, read off the state machine. Consumers used to report a\n * literal `false` here and render \"Available\" over a socket that had never\n * connected; a toggle that cannot distinguish \"not live\" from \"cannot tell\n * yet\" is the shape of that bug.\n */\n isConnecting(): boolean {\n return this.state.connecting;\n }\n\n /** The last failure the socket gave up on, or null. See {@link SocketState.lastError}. */\n getLastError(): string | null {\n return this.state.lastError;\n }\n\n protected ensureConnection(): void {\n const session = this.resolveSession();\n if (!session) return;\n if (typeof window === 'undefined' || typeof WebSocket === 'undefined') return;\n if (this.state.ws || this.state.connecting) return;\n\n this.clearCloseTimer();\n this.state.connecting = true;\n this.state.intentionalClose = false;\n // `connecting` is part of what consumers render, so entering it is a\n // change they have to hear about. Without this notify a `useSyncExternal`\n // store reading `isConnecting()` would only ever sample it on some later\n // unrelated transition.\n this.notifyConnectionListeners();\n\n const ws = new WebSocket(this.buildUrl(session));\n\n ws.onopen = () => {\n ws.send(JSON.stringify({ type: 'login', token: session.accessToken }));\n };\n\n ws.onmessage = (event: MessageEvent) => {\n // Frames from a socket this client has already replaced belong to\n // nobody: acting on them would fold a stale connection's\n // subscription state into the live one.\n if (this.state.ws !== null && this.state.ws !== ws) return;\n try {\n const msg = JSON.parse(String(event.data)) as ProtocolInitMessage & {\n type?: string;\n channels?: string[];\n channel?: string;\n event?: ChannelEvent;\n errors?: unknown[];\n };\n\n if (msg.type === 'protocol.init' && !this.state.connected) {\n this.state.connected = true;\n this.readProtocolInit(msg);\n this.state.connecting = false;\n this.state.reconnectAttempts = 0;\n this.state.lastError = null;\n\n if (this.state.heartbeatTimer) clearInterval(this.state.heartbeatTimer);\n this.state.heartbeatTimer = setInterval(() => {\n if (this.state.ws?.readyState === WebSocket.OPEN) {\n this.state.ws.send(JSON.stringify({ type: 'heartbeat' }));\n }\n }, HEARTBEAT_INTERVAL_MS);\n\n if (this.state.listeners.size > 0) {\n ws.send(\n JSON.stringify({\n type: 'subscribe',\n channels: Array.from(this.state.listeners.keys()),\n })\n );\n }\n\n this.notifyConnectionListeners();\n return;\n }\n\n if (msg.type === 'subscribed' && Array.isArray(msg.channels)) {\n for (const ch of msg.channels) {\n this.state.subscribedChannels.add(ch);\n this.clearSubscriptionRetry(ch);\n }\n this.state.subscriptionRevision += 1;\n this.notifyConnectionListeners();\n return;\n }\n\n if (msg.type === 'subscribe_error' && Array.isArray(msg.errors)) {\n const retryableChannels: string[] = [];\n for (const rawError of msg.errors) {\n if (!rawError || typeof rawError !== 'object') continue;\n const error = rawError as { channel?: unknown; retryable?: unknown };\n if (typeof error.channel !== 'string') continue;\n\n if (error.retryable === true) {\n retryableChannels.push(error.channel);\n } else {\n this.clearSubscriptionRetry(error.channel);\n }\n }\n this.scheduleSubscriptionRetry(retryableChannels);\n return;\n }\n\n if (msg.type === 'unsubscribed' && Array.isArray(msg.channels)) {\n // The server also sends this unprompted when a grant change\n // revoked a subscription (account socket). Forgetting the\n // channel is what makes the client's next `subscribe` a real\n // re-authorization rather than a no-op against a stale set.\n for (const ch of msg.channels) {\n this.state.subscribedChannels.delete(ch);\n }\n this.state.subscriptionRevision += 1;\n this.notifyConnectionListeners();\n this.scheduleSubscriptionRetry(msg.channels);\n return;\n }\n\n if (msg.type === 'error') {\n // The server's own refusal — a rejected login, an unknown\n // message shape. Recorded rather than dropped: it is the one\n // failure the transport cannot tell the consumer about, because\n // the socket is open and will close looking like any other.\n const message = (msg as { message?: unknown }).message;\n this.state.lastError =\n typeof message === 'string' && message ? message : 'Realtime error';\n this.notifyConnectionListeners();\n return;\n }\n\n if (msg.type === 'event' && msg.channel && msg.event) {\n const channelListeners = this.state.listeners.get(msg.channel);\n if (channelListeners) {\n for (const channelListener of channelListeners) {\n try {\n channelListener(msg.event);\n } catch (err) {\n console.error('Channel listener error:', err);\n }\n }\n }\n }\n } catch {\n // Ignore malformed frames.\n }\n };\n\n ws.onclose = () => {\n // A close from a socket that is no longer this client's would\n // otherwise clear the REPLACEMENT connection's state: null its `ws`,\n // wipe its subscribed set, and stop its heartbeat — leaving a live\n // socket that believes it is subscribed to nothing and a client that\n // believes it is disconnected. Reachable whenever a `setSession`\n // replaced the socket while the old one's close was still in flight.\n if (this.state.ws !== null && this.state.ws !== ws) return;\n\n this.state.connected = false;\n this.state.connecting = false;\n this.state.ws = null;\n this.state.subscribedChannels.clear();\n this.clearSubscriptionRetry();\n\n if (this.state.heartbeatTimer) {\n clearInterval(this.state.heartbeatTimer);\n this.state.heartbeatTimer = null;\n }\n\n const willRetry =\n !this.state.intentionalClose &&\n this.state.listeners.size > 0 &&\n this.state.reconnectAttempts < MAX_RECONNECT_ATTEMPTS;\n\n if (!willRetry && !this.state.intentionalClose && this.state.listeners.size > 0) {\n // Out of attempts with listeners still waiting: the client has\n // genuinely given up, which is the moment worth surfacing. Each\n // transient close on the way here is not — a socket that reconnects\n // in three seconds did not fail, and reporting it would train\n // people to ignore the indicator.\n this.state.lastError = 'Realtime connection lost';\n }\n\n this.notifyConnectionListeners();\n\n if (willRetry) {\n this.state.reconnectAttempts += 1;\n this.state.reconnectTimer = setTimeout(() => {\n this.ensureConnection();\n }, RECONNECT_DELAY_MS * this.state.reconnectAttempts);\n }\n };\n\n ws.onerror = () => {\n // onclose will fire after this\n };\n\n this.state.ws = ws;\n }\n\n protected closeConnection(): void {\n this.state.intentionalClose = true;\n this.clearCloseTimer();\n if (this.state.reconnectTimer) {\n clearTimeout(this.state.reconnectTimer);\n this.state.reconnectTimer = null;\n }\n if (this.state.heartbeatTimer) {\n clearInterval(this.state.heartbeatTimer);\n this.state.heartbeatTimer = null;\n }\n this.state.ws?.close();\n this.state.ws = null;\n this.state.connected = false;\n // Only a deliberate close forgets who we were; a transient `onclose`\n // leaves the identity in place because it is unchanged across a\n // reconnect, and the next `protocol.init` overwrites it anyway.\n this.clearIdentity();\n this.state.connecting = false;\n this.state.subscribedChannels.clear();\n this.clearSubscriptionRetry();\n this.notifyConnectionListeners();\n }\n}\n","/**\n * Unified workspace WebSocket client for real-time events.\n *\n * Mirrors `/api/v2/w/{wid}/ws/realtime`: login with OAuth access token,\n * subscribe to channels, receive AGUI events.\n *\n * The socket state machine — login, heartbeat, reconnect, subscribe retry —\n * lives in {@link RealtimeSocketClient}, shared with the account client. This\n * file is what is specific to the workspace mount: its URL, and the member id\n * `protocol.init` hands back.\n */\n\nimport { RealtimeSocketClient, type ProtocolInitMessage } from './socket-client';\nimport type { ChannelListener, RealtimeAuthSession, Unsubscribe } from './types';\n\n/** Build the workspace realtime WebSocket URL from session config. */\nexport function buildWorkspaceWsUrl(session: RealtimeAuthSession): string {\n const isSsl = session.apiUrl.startsWith('https');\n const host = session.apiUrl.replace(/^https?:\\/\\//, '');\n return `${isSsl ? 'wss' : 'ws'}://${host}/api/v2/w/${session.workspaceUuid}/ws/realtime`;\n}\n\nexport class WorkspaceRealtimeClient extends RealtimeSocketClient<RealtimeAuthSession> {\n private memberId: number | null = null;\n\n protected buildUrl(session: RealtimeAuthSession): string {\n return buildWorkspaceWsUrl(session);\n }\n\n protected readProtocolInit(msg: ProtocolInitMessage): void {\n this.memberId = typeof msg.memberId === 'number' ? msg.memberId : null;\n }\n\n protected clearIdentity(): void {\n this.memberId = null;\n }\n\n protected sessionRequiresReconnect(\n previous: RealtimeAuthSession | null,\n next: RealtimeAuthSession\n ): boolean {\n return (\n super.sessionRequiresReconnect(previous, next) ||\n previous?.workspaceUuid !== next.workspaceUuid\n );\n }\n\n /**\n * Member id the server authenticated this socket as, or null before\n * `protocol.init` arrives.\n *\n * Channels whose Redis key is workspace-wide can carry events addressed to\n * one member (presence acks, for example). Consumers compare the event's\n * addressee against this so a reply meant for another operator cannot be\n * applied locally.\n */\n getMemberId(): number | null {\n return this.memberId;\n }\n}\n\nconst defaultClient = new WorkspaceRealtimeClient();\n\nexport function initWorkspaceSocket(session: RealtimeAuthSession): void {\n defaultClient.setSession(session);\n}\n\nexport function subscribeWorkspaceChannel(channel: string, listener: ChannelListener): Unsubscribe {\n return defaultClient.subscribe(channel, listener);\n}\n\nexport function onWorkspaceSocketConnection(listener: (connected: boolean) => void): () => void {\n return defaultClient.onConnection(listener);\n}\n\nexport function isWorkspaceSocketConnected(): boolean {\n return defaultClient.isConnected();\n}\n","/**\n * Account WebSocket client — one socket per tab, rooted at the Account.\n *\n * Mirrors `/api/v2/ws/realtime`. No workspace in the URL and none in the\n * session: the token is the whole identity, and what the socket may see comes\n * from the server's grant map, refreshed there.\n *\n * See `docs/projects/active/account-socket.md`. The point is not a second\n * socket — it is *one*. An operator covering four workspaces holds four\n * `WorkspaceRealtimeClient`s today, which is four connections, four\n * heartbeats, and no place to stand for \"what is happening across my estate\".\n * Workspace-scoped channels are reached here by qualifying their names, so the\n * same channel handlers serve both mounts and the per-workspace socket can be\n * deleted at cutover rather than lived alongside.\n *\n * ```ts\n * const client = new AccountRealtimeClient({ apiUrl, accessToken });\n * client.subscribe(workspaceChannel(wid, 'activity'), onInvalidate);\n * client.subscribe(ACCOUNT_ACTIVITY_CHANNEL, onInvalidate);\n * ```\n */\n\nimport { RealtimeSocketClient, type ProtocolInitMessage } from './socket-client';\nimport type { AccountRealtimeSession, ChannelListener, Unsubscribe } from './types';\n\n/** Build the account realtime WebSocket URL from session config. */\nexport function buildAccountWsUrl(session: AccountRealtimeSession): string {\n const isSsl = session.apiUrl.startsWith('https');\n const host = session.apiUrl.replace(/^https?:\\/\\//, '');\n return `${isSsl ? 'wss' : 'ws'}://${host}/api/v2/ws/realtime`;\n}\n\n/**\n * Qualify a workspace-scoped channel name for the account socket.\n *\n * `chat:42:monitor` in workspace `W` is `w/W/chat:42:monitor` here. Exported\n * as a function rather than left to callers' template strings for the reason\n * every channel name constant in this package exists: the server matches\n * exactly, so a typo subscribes successfully to nothing.\n */\nexport function workspaceChannel(workspaceUuid: string, channel: string): string {\n return `w/${workspaceUuid}/${channel}`;\n}\n\nexport class AccountRealtimeClient extends RealtimeSocketClient<AccountRealtimeSession> {\n private accountUuid: string | null = null;\n private workspaceUuids: readonly string[] = [];\n\n protected buildUrl(session: AccountRealtimeSession): string {\n return buildAccountWsUrl(session);\n }\n\n protected readProtocolInit(msg: ProtocolInitMessage): void {\n this.accountUuid = typeof msg.accountUuid === 'string' ? msg.accountUuid : null;\n this.workspaceUuids = Array.isArray(msg.workspaceUuids)\n ? msg.workspaceUuids.filter((value): value is string => typeof value === 'string')\n : [];\n }\n\n protected clearIdentity(): void {\n this.accountUuid = null;\n this.workspaceUuids = [];\n }\n\n /** Account uuid the server authenticated this socket as, or null. */\n getAccountUuid(): string | null {\n return this.accountUuid;\n }\n\n /**\n * Workspaces the server's grant map admitted at `protocol.init`.\n *\n * A **hint for rendering**, never an authorization decision: the server\n * re-checks its own map on every subscribe, and refreshes it on a floor, so\n * this snapshot can be stale in either direction. Use it to decide which\n * panes are worth opening, not which data a viewer may see.\n */\n getWorkspaceUuids(): readonly string[] {\n return this.workspaceUuids;\n }\n}\n\nconst defaultClient = new AccountRealtimeClient();\n\nexport function initAccountSocket(session: AccountRealtimeSession): void {\n defaultClient.setSession(session);\n}\n\nexport function subscribeAccountChannel(channel: string, listener: ChannelListener): Unsubscribe {\n return defaultClient.subscribe(channel, listener);\n}\n\nexport function onAccountSocketConnection(listener: (connected: boolean) => void): () => void {\n return defaultClient.onConnection(listener);\n}\n\nexport function isAccountSocketConnected(): boolean {\n return defaultClient.isConnected();\n}\n","/**\n * Human operator session WebSocket client.\n *\n * Connects to `/api/v2/w/{wid}/operator/chats/{chatId}/session-ws` — AGUI\n * events, bidirectional PCM, and call control. No AI inference.\n */\n\nimport {\n base64ToFloat32Array,\n float32ArrayToBase64,\n SERVER_SAMPLE_RATE,\n TARGET_SAMPLE_RATE,\n} from '../audio/pcm';\nimport type {\n ChannelEvent,\n OperatorCallPhase,\n OperatorSessionState,\n RealtimeAuthSession,\n} from './types';\n\nexport interface OperatorSessionHandlers {\n onSessionStateChange?: (state: OperatorSessionState) => void;\n onCallPhaseChange?: (phase: OperatorCallPhase) => void;\n onAguiEvent?: (event: ChannelEvent) => void;\n onError?: (message: string) => void;\n /** Fired when the server closes the session (e.g. callee hung up). */\n onSessionClosed?: (reason?: string) => void;\n}\n\nexport class OperatorSessionClient {\n private ws: WebSocket | null = null;\n private audioContext: AudioContext | null = null;\n private mediaStream: MediaStream | null = null;\n private processor: ScriptProcessorNode | null = null;\n private sessionState: OperatorSessionState = 'idle';\n private callPhase: OperatorCallPhase = 'idle';\n private nextPlaybackTime = 0;\n private playbackSources: AudioBufferSourceNode[] = [];\n private micActive = false;\n private onHold = false;\n private muted = false;\n private receivedRemoteAudio = false;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private sessionCloseNotified = false;\n private intentionalDisconnect = false;\n\n constructor(private handlers: OperatorSessionHandlers = {}) {}\n\n getSessionState(): OperatorSessionState {\n return this.sessionState;\n }\n\n getCallPhase(): OperatorCallPhase {\n return this.callPhase;\n }\n\n private setSessionState(next: OperatorSessionState): void {\n this.sessionState = next;\n this.handlers.onSessionStateChange?.(next);\n }\n\n private setCallPhase(next: OperatorCallPhase): void {\n this.callPhase = next;\n this.handlers.onCallPhaseChange?.(next);\n }\n\n private notifySessionClosed(reason?: string): void {\n if (this.sessionCloseNotified) return;\n this.sessionCloseNotified = true;\n this.handlers.onSessionClosed?.(reason);\n }\n\n async connect(session: RealtimeAuthSession, chatId: number): Promise<void> {\n if (typeof window === 'undefined' || typeof WebSocket === 'undefined') {\n throw new Error('Operator session WebSocket is not available in this environment');\n }\n\n await this.disconnect();\n this.sessionCloseNotified = false;\n this.setSessionState('connecting');\n this.setCallPhase('connecting');\n\n const isSsl = session.apiUrl.startsWith('https');\n const host = session.apiUrl.replace(/^https?:\\/\\//, '');\n const wsUrl = `${isSsl ? 'wss' : 'ws'}://${host}/api/v2/w/${session.workspaceUuid}/operator/chats/${chatId}/session-ws`;\n\n await new Promise<void>((resolve, reject) => {\n const ws = new WebSocket(wsUrl);\n this.ws = ws;\n\n ws.onopen = () => {\n ws.send(JSON.stringify({ type: 'login', token: session.accessToken }));\n };\n\n ws.onmessage = (event) => {\n try {\n const msg = JSON.parse(String(event.data)) as {\n type?: string;\n name?: string;\n value?: Record<string, unknown>;\n message?: string;\n code?: string;\n event?: ChannelEvent;\n };\n this.handleMessage(msg, resolve, reject);\n } catch {\n /* ignore malformed frames */\n }\n };\n\n ws.onerror = () => {\n const message = 'Operator session WebSocket error';\n const wasConnecting = this.sessionState === 'connecting';\n this.setSessionState('error');\n if (wasConnecting) {\n this.setCallPhase('idle');\n reject(new Error(message));\n } else {\n this.setCallPhase('failed');\n this.handlers.onError?.(message);\n }\n };\n\n ws.onclose = () => {\n this.clearHeartbeat();\n this.stopMicrophone();\n this.stopPlayback();\n const wasConnected = this.sessionState === 'connected';\n this.setSessionState('idle');\n this.setCallPhase('idle');\n if (wasConnected && !this.intentionalDisconnect) {\n this.notifySessionClosed('disconnect');\n }\n this.intentionalDisconnect = false;\n };\n });\n }\n\n private handleMessage(\n msg: {\n type?: string;\n name?: string;\n value?: Record<string, unknown>;\n message?: string;\n code?: string;\n event?: ChannelEvent;\n },\n resolveLogin: () => void,\n rejectLogin: (err: Error) => void\n ): void {\n if (msg.type === 'RUN_ERROR' || msg.type === 'error') {\n const message = msg.message ?? 'Operator session error';\n if (this.sessionState === 'connecting') {\n this.setSessionState('error');\n rejectLogin(new Error(message));\n } else {\n this.setCallPhase('failed');\n this.handlers.onError?.(message);\n }\n return;\n }\n\n if (msg.type === 'protocol.init' && this.sessionState === 'connecting') {\n return;\n }\n\n if (msg.type === 'CUSTOM' && msg.name === 'operator.session_ready') {\n this.setSessionState('connected');\n this.setCallPhase('ready');\n this.startHeartbeat();\n resolveLogin();\n return;\n }\n\n if (msg.type === 'event' && msg.event) {\n if (this.handleRemoteVoiceAudio(msg.event)) {\n return;\n }\n this.handlers.onAguiEvent?.(msg.event);\n return;\n }\n\n if (msg.type === 'CUSTOM' && msg.name === 'operator.call.started') {\n this.receivedRemoteAudio = false;\n this.setCallPhase('dialing');\n return;\n }\n\n if (msg.type === 'CUSTOM' && msg.name === 'operator.call.ended') {\n this.onHold = false;\n this.muted = false;\n this.receivedRemoteAudio = false;\n this.stopMicrophone();\n this.stopPlayback();\n const endSession = msg.value?.end_session === true;\n const reason = typeof msg.value?.reason === 'string' ? msg.value.reason : undefined;\n if (endSession) {\n this.setCallPhase('idle');\n this.notifySessionClosed(reason ?? 'peer_hangup');\n } else {\n this.setCallPhase('ready');\n }\n return;\n }\n }\n\n /** Play callee PCM from wrapped chat-stream events; returns true if handled. */\n private handleRemoteVoiceAudio(event: ChannelEvent): boolean {\n if (event.type !== 'CUSTOM' || event.name !== 'voice.audio') {\n return false;\n }\n if (this.onHold) return true;\n\n const value = event.value;\n if (!value || typeof value !== 'object') return true;\n\n const data = (value as Record<string, unknown>).data;\n if (typeof data !== 'string' || !data) return true;\n\n if (\n !this.receivedRemoteAudio &&\n (this.callPhase === 'dialing' || this.callPhase === 'live')\n ) {\n this.receivedRemoteAudio = true;\n this.setCallPhase('live');\n }\n void this.playPcm16(base64ToFloat32Array(data));\n return true;\n }\n\n private startHeartbeat(): void {\n this.clearHeartbeat();\n this.heartbeatTimer = setInterval(() => {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify({ type: 'heartbeat' }));\n }\n }, 30_000);\n }\n\n private clearHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n\n private sendCustom(name: string, value: Record<string, unknown> = {}): void {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n throw new Error('Operator session WebSocket is not connected');\n }\n this.ws.send(JSON.stringify({ type: 'CUSTOM', name, value }));\n }\n\n async placeCall(memberId: number): Promise<void> {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n throw new Error('Operator session WebSocket is not connected');\n }\n if (this.sessionState !== 'connected') {\n throw new Error('Operator session is not ready');\n }\n await this.startMicrophone();\n this.sendCustom('operator.call.place', { member_id: memberId });\n }\n\n /**\n * Answer an inbound operator call the caller has already been routed to.\n *\n * Symmetric with {@link placeCall} but for the inbound direction: instead of\n * dialing an outbound leg it tells the backend to bridge the already-ringing\n * caller to this operator session. The backend (see\n * `lib/operator_session_handler.py::_answer_inbound_call`) registers the\n * audio bridge, marks the ring state `answered`, and replies with\n * `operator.call.started` — after which callee PCM arrives as `voice.audio`\n * events (played via {@link playPcm16}) and mic audio streams the other way,\n * exactly like an outbound call.\n *\n * Must be called once the session is ready (state `connected`). Starts the\n * microphone before sending so the operator's audio is flowing the instant\n * the bridge registers.\n */\n async answerCall(interactionId: string, answerToken: string): Promise<void> {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n throw new Error('Operator session WebSocket is not connected');\n }\n if (this.sessionState !== 'connected') {\n throw new Error('Operator session is not ready');\n }\n await this.startMicrophone();\n this.sendCustom('operator.call.answer', {\n interaction_id: interactionId,\n answer_token: answerToken,\n });\n }\n\n /**\n * Mute or unmute the operator's outbound microphone. Muting gates mic frames\n * locally (the caller stops hearing the operator) while callee audio keeps\n * playing — distinct from {@link setHold}, which is a two-way pause the\n * backend is told about. Purely client-side; no server round-trip.\n */\n setMuted(muted: boolean): void {\n this.muted = muted;\n }\n\n isMuted(): boolean {\n return this.muted;\n }\n\n hangUp(): void {\n try {\n this.sendCustom('operator.call.hangup');\n } catch {\n /* socket already closed */\n }\n this.onHold = false;\n this.receivedRemoteAudio = false;\n }\n\n forwardCall(phoneNumber: string): void {\n this.receivedRemoteAudio = false;\n this.setCallPhase('dialing');\n this.sendCustom('operator.call.forward', { phone_number: phoneNumber });\n }\n\n setHold(onHold: boolean): void {\n this.onHold = onHold;\n this.sendCustom('operator.call.hold', { on_hold: onHold });\n if (this.callPhase === 'live' || this.callPhase === 'onHold') {\n this.setCallPhase(onHold ? 'onHold' : 'live');\n }\n if (onHold) {\n this.stopPlayback();\n }\n }\n\n async startMicrophone(): Promise<void> {\n if (typeof window === 'undefined') return;\n if (!navigator.mediaDevices?.getUserMedia) {\n throw new Error('Microphone not available in this browser');\n }\n if (this.micActive) return;\n\n this.mediaStream = await navigator.mediaDevices.getUserMedia({\n audio: {\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n },\n });\n\n const Ctx =\n window.AudioContext ??\n (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Ctx) throw new Error('Web Audio not supported');\n\n this.audioContext = new Ctx();\n if (this.audioContext.state === 'suspended') {\n await this.audioContext.resume();\n }\n\n const source = this.audioContext.createMediaStreamSource(this.mediaStream);\n const processor = this.audioContext.createScriptProcessor(4096, 1, 1);\n this.processor = processor;\n\n processor.onaudioprocess = (event) => {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;\n if (this.onHold) return;\n if (this.muted) return;\n if (\n this.callPhase !== 'dialing' &&\n this.callPhase !== 'live' &&\n this.callPhase !== 'onHold'\n ) {\n return;\n }\n\n const input = event.inputBuffer.getChannelData(0);\n const copy = new Float32Array(input.length);\n copy.set(input);\n\n const sampleRate = this.audioContext?.sampleRate ?? TARGET_SAMPLE_RATE;\n let pcm = copy;\n if (sampleRate !== TARGET_SAMPLE_RATE) {\n const ratio = sampleRate / TARGET_SAMPLE_RATE;\n const newLen = Math.floor(copy.length / ratio);\n const down = new Float32Array(newLen);\n for (let i = 0; i < newLen; i += 1) {\n down[i] = copy[Math.floor(i * ratio)];\n }\n pcm = down;\n }\n\n this.ws.send(\n JSON.stringify({\n type: 'CUSTOM',\n name: 'voice.audio',\n value: {\n data: float32ArrayToBase64(pcm),\n format: 'pcm16',\n sample_rate: TARGET_SAMPLE_RATE,\n },\n })\n );\n };\n\n source.connect(processor);\n processor.connect(this.audioContext.destination);\n this.micActive = true;\n }\n\n stopMicrophone(): void {\n this.processor?.disconnect();\n this.processor = null;\n for (const track of this.mediaStream?.getTracks() ?? []) {\n track.stop();\n }\n this.mediaStream = null;\n this.micActive = false;\n }\n\n private async playPcm16(samples: Float32Array): Promise<void> {\n if (typeof window === 'undefined') return;\n\n const Ctx =\n window.AudioContext ??\n (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Ctx) return;\n\n if (!this.audioContext || this.audioContext.state === 'closed') {\n this.audioContext = new Ctx();\n }\n if (this.audioContext.state === 'suspended') {\n await this.audioContext.resume();\n }\n\n const buffer = this.audioContext.createBuffer(1, samples.length, SERVER_SAMPLE_RATE);\n buffer.copyToChannel(new Float32Array(samples), 0);\n\n const source = this.audioContext.createBufferSource();\n source.buffer = buffer;\n source.connect(this.audioContext.destination);\n\n const now = this.audioContext.currentTime;\n const startAt = this.nextPlaybackTime < now + 0.02 ? now + 0.02 : this.nextPlaybackTime;\n source.start(startAt);\n this.nextPlaybackTime = startAt + buffer.duration;\n this.playbackSources.push(source);\n source.onended = () => {\n const idx = this.playbackSources.indexOf(source);\n if (idx >= 0) this.playbackSources.splice(idx, 1);\n };\n }\n\n private stopPlayback(): void {\n for (const src of this.playbackSources) {\n try {\n src.stop();\n } catch {\n /* already stopped */\n }\n }\n this.playbackSources = [];\n this.nextPlaybackTime = 0;\n }\n\n async disconnect(): Promise<void> {\n this.intentionalDisconnect = true;\n if (\n this.callPhase === 'dialing' ||\n this.callPhase === 'live' ||\n this.callPhase === 'onHold'\n ) {\n this.hangUp();\n }\n this.clearHeartbeat();\n this.stopMicrophone();\n this.stopPlayback();\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n if (this.audioContext && this.audioContext.state !== 'closed') {\n await this.audioContext.close().catch(() => undefined);\n }\n this.audioContext = null;\n this.onHold = false;\n this.muted = false;\n this.receivedRemoteAudio = false;\n this.setSessionState('idle');\n this.setCallPhase('idle');\n }\n}\n"],"mappings":";;;;;;;;AAoCO,SAAS,kBAAkB,OAAmC;AAClE,SAAO,UAAU,aAAa,UAAU,UAAU,UAAU;AAC/D;AAgBO,IAAM,mBAAmB;AACzB,IAAM,0BAA0B;AAUhC,IAAM,2BAA2B;AAkBjC,IAAM,2BAA2B;AAGjC,IAAM,mBAAmB;AAuBzB,SAAS,uBAAuB,OAAyD;AAC7F,MAAI,MAAM,SAAS,aAAc,QAAO;AACxC,QAAM,OAAQ,MAA6B;AAC3C,MAAI,OAAO,SAAS,YAAY,SAAS,KAAM,QAAO;AACtD,QAAM,QAAS,KAA6B;AAC5C,SAAO,UAAU,eAAe,UAAU,YAAY,UAAU;AACnE;;;AC7FA,IAAM,wBAAwB;AAC9B,IAAM,qBAAqB;AAC3B,IAAM,yBAAyB;AAC/B,IAAM,iBAAiB;AACvB,IAAM,+BAA+B;AA8BrC,SAAS,oBAAiC;AACvC,SAAO;AAAA,IACJ,IAAI;AAAA,IACJ,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,WAAW;AAAA,IACX,WAAW,oBAAI,IAAI;AAAA,IACnB,oBAAoB,oBAAI,IAAI;AAAA,IAC5B,sBAAsB;AAAA,IACtB,2BAA2B,oBAAI,IAAI;AAAA,IACnC,2BAA2B;AAAA,IAC3B,wBAAwB;AAAA,IACxB,mBAAmB;AAAA,IACnB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,YAAY;AAAA,IACZ,kBAAkB;AAAA,EACrB;AACH;AASO,IAAe,uBAAf,MAA0E;AAAA,EACtE,UAA2B;AAAA,EAClB;AAAA,EACA,QAAqB,kBAAkB;AAAA,EACvC,sBAAsB,oBAAI,IAAkC;AAAA,EAE7E,YAAY,QAA6C;AACtD,QAAI,OAAO,WAAW,YAAY;AAC/B,WAAK,eAAe;AAAA,IACvB,OAAO;AACJ,WAAK,eAAe;AACpB,UAAI,QAAQ;AACT,aAAK,UAAU;AAAA,MAClB;AAAA,IACH;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuBU,yBAAyB,UAA2B,MAAyB;AACpF,WAAO,UAAU,gBAAgB,KAAK,eAAe,UAAU,WAAW,KAAK;AAAA,EAClF;AAAA,EAEU,iBAAkC;AACzC,WAAO,KAAK,WAAW,KAAK,eAAe,KAAK;AAAA,EACnD;AAAA,EAEQ,4BAAkC;AACvC,eAAW,YAAY,KAAK,qBAAqB;AAC9C,eAAS,KAAK,MAAM,SAAS;AAAA,IAChC;AAAA,EACH;AAAA,EAEQ,kBAAwB;AAC7B,QAAI,KAAK,MAAM,YAAY;AACxB,mBAAa,KAAK,MAAM,UAAU;AAClC,WAAK,MAAM,aAAa;AAAA,IAC3B;AAAA,EACH;AAAA,EAEQ,uBAAuB,SAAwB;AACpD,QAAI,SAAS;AACV,WAAK,MAAM,0BAA0B,OAAO,OAAO;AAAA,IACtD,OAAO;AACJ,WAAK,MAAM,0BAA0B,MAAM;AAAA,IAC9C;AAEA,QAAI,KAAK,MAAM,0BAA0B,OAAO,EAAG;AAEnD,QAAI,KAAK,MAAM,wBAAwB;AACpC,mBAAa,KAAK,MAAM,sBAAsB;AAC9C,WAAK,MAAM,yBAAyB;AAAA,IACvC;AACA,SAAK,MAAM,4BAA4B;AAAA,EAC1C;AAAA,EAEQ,0BAA0B,UAA0B;AACzD,eAAW,WAAW,UAAU;AAC7B,UAAI,KAAK,MAAM,UAAU,IAAI,OAAO,KAAK,CAAC,KAAK,MAAM,mBAAmB,IAAI,OAAO,GAAG;AACnF,aAAK,MAAM,0BAA0B,IAAI,OAAO;AAAA,MACnD;AAAA,IACH;AAEA,QAAI,KAAK,MAAM,0BAA0B,SAAS,KAAK,KAAK,MAAM,wBAAwB;AACvF;AAAA,IACH;AAEA,SAAK,MAAM,6BAA6B;AACxC,UAAM,QAAQ,KAAK;AAAA,MAChB,qBAAqB,KAAK,MAAM;AAAA,MAChC;AAAA,IACH;AAEA,SAAK,MAAM,yBAAyB,WAAW,MAAM;AAClD,WAAK,MAAM,yBAAyB;AACpC,YAAM,gBAAgB,MAAM,KAAK,KAAK,MAAM,yBAAyB,EAAE;AAAA,QACpE,CAAC,YACE,KAAK,MAAM,UAAU,IAAI,OAAO,KAAK,CAAC,KAAK,MAAM,mBAAmB,IAAI,OAAO;AAAA,MACrF;AAEA,iBAAW,WAAW,KAAK,MAAM,2BAA2B;AACzD,YAAI,CAAC,cAAc,SAAS,OAAO,GAAG;AACnC,eAAK,MAAM,0BAA0B,OAAO,OAAO;AAAA,QACtD;AAAA,MACH;AAEA,UAAI,cAAc,WAAW,GAAG;AAC7B,aAAK,MAAM,4BAA4B;AACvC;AAAA,MACH;AAEA,UAAI,KAAK,MAAM,aAAa,KAAK,MAAM,IAAI,eAAe,UAAU,MAAM;AACvE,aAAK,MAAM,GAAG,KAAK,KAAK,UAAU,EAAE,MAAM,aAAa,UAAU,cAAc,CAAC,CAAC;AAAA,MACpF;AAAA,IACH,GAAG,KAAK;AAAA,EACX;AAAA,EAEA,WAAW,SAAyB;AACjC,UAAM,UAAU,KAAK,yBAAyB,KAAK,SAAS,OAAO;AAEnE,SAAK,UAAU;AACf,QAAI,WAAW,KAAK,MAAM,IAAI;AAC3B,WAAK,gBAAgB;AAAA,IACxB;AACA,QAAI,KAAK,MAAM,UAAU,OAAO,GAAG;AAChC,WAAK,iBAAiB;AAAA,IACzB;AAAA,EACH;AAAA,EAEA,UAAU,SAAiB,UAAwC;AAChE,SAAK,gBAAgB;AAErB,QAAI,YAAY,KAAK,MAAM,UAAU,IAAI,OAAO;AAChD,QAAI,CAAC,WAAW;AACb,kBAAY,oBAAI,IAAI;AACpB,WAAK,MAAM,UAAU,IAAI,SAAS,SAAS;AAAA,IAC9C;AACA,cAAU,IAAI,QAAQ;AAEtB,SAAK,iBAAiB;AAEtB,QACG,KAAK,MAAM,aACX,KAAK,MAAM,IAAI,eAAe,UAAU,QACxC,CAAC,KAAK,MAAM,mBAAmB,IAAI,OAAO,GAC3C;AACC,WAAK,MAAM,GAAG,KAAK,KAAK,UAAU,EAAE,MAAM,aAAa,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC;AAAA,IAChF;AAEA,WAAO,MAAM;AACV,YAAM,mBAAmB,KAAK,MAAM,UAAU,IAAI,OAAO;AACzD,wBAAkB,OAAO,QAAQ;AACjC,UAAI,oBAAoB,iBAAiB,SAAS,GAAG;AAClD,aAAK,MAAM,UAAU,OAAO,OAAO;AACnC,aAAK,MAAM,mBAAmB,OAAO,OAAO;AAC5C,aAAK,uBAAuB,OAAO;AACnC,YAAI,KAAK,MAAM,aAAa,KAAK,MAAM,IAAI,eAAe,UAAU,MAAM;AACvE,eAAK,MAAM,GAAG,KAAK,KAAK,UAAU,EAAE,MAAM,eAAe,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC;AAAA,QAClF;AAAA,MACH;AAEA,UAAI,KAAK,MAAM,UAAU,SAAS,GAAG;AAClC,aAAK,MAAM,aAAa,WAAW,MAAM;AACtC,cAAI,KAAK,MAAM,UAAU,SAAS,GAAG;AAClC,iBAAK,gBAAgB;AAAA,UACxB;AAAA,QACH,GAAG,cAAc;AAAA,MACpB;AAAA,IACH;AAAA,EACH;AAAA,EAEA,KAAK,SAAiB,MAAqB;AACxC,QAAI,CAAC,KAAK,MAAM,aAAa,CAAC,KAAK,MAAM,GAAI;AAC7C,SAAK,MAAM,GAAG,KAAK,KAAK,UAAU,EAAE,MAAM,WAAW,SAAS,KAAK,CAAC,CAAC;AAAA,EACxE;AAAA,EAEA,aAAa,UAAoD;AAC9D,SAAK,oBAAoB,IAAI,QAAQ;AACrC,aAAS,KAAK,MAAM,SAAS;AAC7B,WAAO,MAAM;AACV,WAAK,oBAAoB,OAAO,QAAQ;AAAA,IAC3C;AAAA,EACH;AAAA,EAEA,cAAuB;AACpB,WAAO,KAAK,MAAM;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,aAAa,SAA0B;AACpC,WAAO,KAAK,MAAM,mBAAmB,IAAI,OAAO;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,0BAAkC;AAC/B,WAAO,KAAK,MAAM;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,eAAwB;AACrB,WAAO,KAAK,MAAM;AAAA,EACrB;AAAA;AAAA,EAGA,eAA8B;AAC3B,WAAO,KAAK,MAAM;AAAA,EACrB;AAAA,EAEU,mBAAyB;AAChC,UAAM,UAAU,KAAK,eAAe;AACpC,QAAI,CAAC,QAAS;AACd,QAAI,OAAO,WAAW,eAAe,OAAO,cAAc,YAAa;AACvE,QAAI,KAAK,MAAM,MAAM,KAAK,MAAM,WAAY;AAE5C,SAAK,gBAAgB;AACrB,SAAK,MAAM,aAAa;AACxB,SAAK,MAAM,mBAAmB;AAK9B,SAAK,0BAA0B;AAE/B,UAAM,KAAK,IAAI,UAAU,KAAK,SAAS,OAAO,CAAC;AAE/C,OAAG,SAAS,MAAM;AACf,SAAG,KAAK,KAAK,UAAU,EAAE,MAAM,SAAS,OAAO,QAAQ,YAAY,CAAC,CAAC;AAAA,IACxE;AAEA,OAAG,YAAY,CAAC,UAAwB;AAIrC,UAAI,KAAK,MAAM,OAAO,QAAQ,KAAK,MAAM,OAAO,GAAI;AACpD,UAAI;AACD,cAAM,MAAM,KAAK,MAAM,OAAO,MAAM,IAAI,CAAC;AAQzC,YAAI,IAAI,SAAS,mBAAmB,CAAC,KAAK,MAAM,WAAW;AACxD,eAAK,MAAM,YAAY;AACvB,eAAK,iBAAiB,GAAG;AACzB,eAAK,MAAM,aAAa;AACxB,eAAK,MAAM,oBAAoB;AAC/B,eAAK,MAAM,YAAY;AAEvB,cAAI,KAAK,MAAM,eAAgB,eAAc,KAAK,MAAM,cAAc;AACtE,eAAK,MAAM,iBAAiB,YAAY,MAAM;AAC3C,gBAAI,KAAK,MAAM,IAAI,eAAe,UAAU,MAAM;AAC/C,mBAAK,MAAM,GAAG,KAAK,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC,CAAC;AAAA,YAC3D;AAAA,UACH,GAAG,qBAAqB;AAExB,cAAI,KAAK,MAAM,UAAU,OAAO,GAAG;AAChC,eAAG;AAAA,cACA,KAAK,UAAU;AAAA,gBACZ,MAAM;AAAA,gBACN,UAAU,MAAM,KAAK,KAAK,MAAM,UAAU,KAAK,CAAC;AAAA,cACnD,CAAC;AAAA,YACJ;AAAA,UACH;AAEA,eAAK,0BAA0B;AAC/B;AAAA,QACH;AAEA,YAAI,IAAI,SAAS,gBAAgB,MAAM,QAAQ,IAAI,QAAQ,GAAG;AAC3D,qBAAW,MAAM,IAAI,UAAU;AAC5B,iBAAK,MAAM,mBAAmB,IAAI,EAAE;AACpC,iBAAK,uBAAuB,EAAE;AAAA,UACjC;AACA,eAAK,MAAM,wBAAwB;AACnC,eAAK,0BAA0B;AAC/B;AAAA,QACH;AAEA,YAAI,IAAI,SAAS,qBAAqB,MAAM,QAAQ,IAAI,MAAM,GAAG;AAC9D,gBAAM,oBAA8B,CAAC;AACrC,qBAAW,YAAY,IAAI,QAAQ;AAChC,gBAAI,CAAC,YAAY,OAAO,aAAa,SAAU;AAC/C,kBAAM,QAAQ;AACd,gBAAI,OAAO,MAAM,YAAY,SAAU;AAEvC,gBAAI,MAAM,cAAc,MAAM;AAC3B,gCAAkB,KAAK,MAAM,OAAO;AAAA,YACvC,OAAO;AACJ,mBAAK,uBAAuB,MAAM,OAAO;AAAA,YAC5C;AAAA,UACH;AACA,eAAK,0BAA0B,iBAAiB;AAChD;AAAA,QACH;AAEA,YAAI,IAAI,SAAS,kBAAkB,MAAM,QAAQ,IAAI,QAAQ,GAAG;AAK7D,qBAAW,MAAM,IAAI,UAAU;AAC5B,iBAAK,MAAM,mBAAmB,OAAO,EAAE;AAAA,UAC1C;AACA,eAAK,MAAM,wBAAwB;AACnC,eAAK,0BAA0B;AAC/B,eAAK,0BAA0B,IAAI,QAAQ;AAC3C;AAAA,QACH;AAEA,YAAI,IAAI,SAAS,SAAS;AAKvB,gBAAM,UAAW,IAA8B;AAC/C,eAAK,MAAM,YACR,OAAO,YAAY,YAAY,UAAU,UAAU;AACtD,eAAK,0BAA0B;AAC/B;AAAA,QACH;AAEA,YAAI,IAAI,SAAS,WAAW,IAAI,WAAW,IAAI,OAAO;AACnD,gBAAM,mBAAmB,KAAK,MAAM,UAAU,IAAI,IAAI,OAAO;AAC7D,cAAI,kBAAkB;AACnB,uBAAW,mBAAmB,kBAAkB;AAC7C,kBAAI;AACD,gCAAgB,IAAI,KAAK;AAAA,cAC5B,SAAS,KAAK;AACX,wBAAQ,MAAM,2BAA2B,GAAG;AAAA,cAC/C;AAAA,YACH;AAAA,UACH;AAAA,QACH;AAAA,MACH,QAAQ;AAAA,MAER;AAAA,IACH;AAEA,OAAG,UAAU,MAAM;AAOhB,UAAI,KAAK,MAAM,OAAO,QAAQ,KAAK,MAAM,OAAO,GAAI;AAEpD,WAAK,MAAM,YAAY;AACvB,WAAK,MAAM,aAAa;AACxB,WAAK,MAAM,KAAK;AAChB,WAAK,MAAM,mBAAmB,MAAM;AACpC,WAAK,uBAAuB;AAE5B,UAAI,KAAK,MAAM,gBAAgB;AAC5B,sBAAc,KAAK,MAAM,cAAc;AACvC,aAAK,MAAM,iBAAiB;AAAA,MAC/B;AAEA,YAAM,YACH,CAAC,KAAK,MAAM,oBACZ,KAAK,MAAM,UAAU,OAAO,KAC5B,KAAK,MAAM,oBAAoB;AAElC,UAAI,CAAC,aAAa,CAAC,KAAK,MAAM,oBAAoB,KAAK,MAAM,UAAU,OAAO,GAAG;AAM9E,aAAK,MAAM,YAAY;AAAA,MAC1B;AAEA,WAAK,0BAA0B;AAE/B,UAAI,WAAW;AACZ,aAAK,MAAM,qBAAqB;AAChC,aAAK,MAAM,iBAAiB,WAAW,MAAM;AAC1C,eAAK,iBAAiB;AAAA,QACzB,GAAG,qBAAqB,KAAK,MAAM,iBAAiB;AAAA,MACvD;AAAA,IACH;AAEA,OAAG,UAAU,MAAM;AAAA,IAEnB;AAEA,SAAK,MAAM,KAAK;AAAA,EACnB;AAAA,EAEU,kBAAwB;AAC/B,SAAK,MAAM,mBAAmB;AAC9B,SAAK,gBAAgB;AACrB,QAAI,KAAK,MAAM,gBAAgB;AAC5B,mBAAa,KAAK,MAAM,cAAc;AACtC,WAAK,MAAM,iBAAiB;AAAA,IAC/B;AACA,QAAI,KAAK,MAAM,gBAAgB;AAC5B,oBAAc,KAAK,MAAM,cAAc;AACvC,WAAK,MAAM,iBAAiB;AAAA,IAC/B;AACA,SAAK,MAAM,IAAI,MAAM;AACrB,SAAK,MAAM,KAAK;AAChB,SAAK,MAAM,YAAY;AAIvB,SAAK,cAAc;AACnB,SAAK,MAAM,aAAa;AACxB,SAAK,MAAM,mBAAmB,MAAM;AACpC,SAAK,uBAAuB;AAC5B,SAAK,0BAA0B;AAAA,EAClC;AACH;;;ACtfO,SAAS,oBAAoB,SAAsC;AACvE,QAAM,QAAQ,QAAQ,OAAO,WAAW,OAAO;AAC/C,QAAM,OAAO,QAAQ,OAAO,QAAQ,gBAAgB,EAAE;AACtD,SAAO,GAAG,QAAQ,QAAQ,IAAI,MAAM,IAAI,aAAa,QAAQ,aAAa;AAC7E;AAEO,IAAM,0BAAN,cAAsC,qBAA0C;AAAA,EAC5E,WAA0B;AAAA,EAExB,SAAS,SAAsC;AACtD,WAAO,oBAAoB,OAAO;AAAA,EACrC;AAAA,EAEU,iBAAiB,KAAgC;AACxD,SAAK,WAAW,OAAO,IAAI,aAAa,WAAW,IAAI,WAAW;AAAA,EACrE;AAAA,EAEU,gBAAsB;AAC7B,SAAK,WAAW;AAAA,EACnB;AAAA,EAEU,yBACP,UACA,MACQ;AACR,WACG,MAAM,yBAAyB,UAAU,IAAI,KAC7C,UAAU,kBAAkB,KAAK;AAAA,EAEvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,cAA6B;AAC1B,WAAO,KAAK;AAAA,EACf;AACH;AAEA,IAAM,gBAAgB,IAAI,wBAAwB;AAE3C,SAAS,oBAAoB,SAAoC;AACrE,gBAAc,WAAW,OAAO;AACnC;AAEO,SAAS,0BAA0B,SAAiB,UAAwC;AAChG,SAAO,cAAc,UAAU,SAAS,QAAQ;AACnD;AAEO,SAAS,4BAA4B,UAAoD;AAC7F,SAAO,cAAc,aAAa,QAAQ;AAC7C;AAEO,SAAS,6BAAsC;AACnD,SAAO,cAAc,YAAY;AACpC;;;ACnDO,SAAS,kBAAkB,SAAyC;AACxE,QAAM,QAAQ,QAAQ,OAAO,WAAW,OAAO;AAC/C,QAAM,OAAO,QAAQ,OAAO,QAAQ,gBAAgB,EAAE;AACtD,SAAO,GAAG,QAAQ,QAAQ,IAAI,MAAM,IAAI;AAC3C;AAUO,SAAS,iBAAiB,eAAuB,SAAyB;AAC9E,SAAO,KAAK,aAAa,IAAI,OAAO;AACvC;AAEO,IAAM,wBAAN,cAAoC,qBAA6C;AAAA,EAC7E,cAA6B;AAAA,EAC7B,iBAAoC,CAAC;AAAA,EAEnC,SAAS,SAAyC;AACzD,WAAO,kBAAkB,OAAO;AAAA,EACnC;AAAA,EAEU,iBAAiB,KAAgC;AACxD,SAAK,cAAc,OAAO,IAAI,gBAAgB,WAAW,IAAI,cAAc;AAC3E,SAAK,iBAAiB,MAAM,QAAQ,IAAI,cAAc,IACjD,IAAI,eAAe,OAAO,CAAC,UAA2B,OAAO,UAAU,QAAQ,IAC/E,CAAC;AAAA,EACT;AAAA,EAEU,gBAAsB;AAC7B,SAAK,cAAc;AACnB,SAAK,iBAAiB,CAAC;AAAA,EAC1B;AAAA;AAAA,EAGA,iBAAgC;AAC7B,WAAO,KAAK;AAAA,EACf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,oBAAuC;AACpC,WAAO,KAAK;AAAA,EACf;AACH;AAEA,IAAMA,iBAAgB,IAAI,sBAAsB;AAEzC,SAAS,kBAAkB,SAAuC;AACtE,EAAAA,eAAc,WAAW,OAAO;AACnC;AAEO,SAAS,wBAAwB,SAAiB,UAAwC;AAC9F,SAAOA,eAAc,UAAU,SAAS,QAAQ;AACnD;AAEO,SAAS,0BAA0B,UAAoD;AAC3F,SAAOA,eAAc,aAAa,QAAQ;AAC7C;AAEO,SAAS,2BAAoC;AACjD,SAAOA,eAAc,YAAY;AACpC;;;ACrEO,IAAM,wBAAN,MAA4B;AAAA,EAiBhC,YAAoB,WAAoC,CAAC,GAAG;AAAxC;AAAA,EAAyC;AAAA,EAAzC;AAAA,EAhBZ,KAAuB;AAAA,EACvB,eAAoC;AAAA,EACpC,cAAkC;AAAA,EAClC,YAAwC;AAAA,EACxC,eAAqC;AAAA,EACrC,YAA+B;AAAA,EAC/B,mBAAmB;AAAA,EACnB,kBAA2C,CAAC;AAAA,EAC5C,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,sBAAsB;AAAA,EACtB,iBAAwD;AAAA,EACxD,uBAAuB;AAAA,EACvB,wBAAwB;AAAA,EAIhC,kBAAwC;AACrC,WAAO,KAAK;AAAA,EACf;AAAA,EAEA,eAAkC;AAC/B,WAAO,KAAK;AAAA,EACf;AAAA,EAEQ,gBAAgB,MAAkC;AACvD,SAAK,eAAe;AACpB,SAAK,SAAS,uBAAuB,IAAI;AAAA,EAC5C;AAAA,EAEQ,aAAa,MAA+B;AACjD,SAAK,YAAY;AACjB,SAAK,SAAS,oBAAoB,IAAI;AAAA,EACzC;AAAA,EAEQ,oBAAoB,QAAuB;AAChD,QAAI,KAAK,qBAAsB;AAC/B,SAAK,uBAAuB;AAC5B,SAAK,SAAS,kBAAkB,MAAM;AAAA,EACzC;AAAA,EAEA,MAAM,QAAQ,SAA8B,QAA+B;AACxE,QAAI,OAAO,WAAW,eAAe,OAAO,cAAc,aAAa;AACpE,YAAM,IAAI,MAAM,iEAAiE;AAAA,IACpF;AAEA,UAAM,KAAK,WAAW;AACtB,SAAK,uBAAuB;AAC5B,SAAK,gBAAgB,YAAY;AACjC,SAAK,aAAa,YAAY;AAE9B,UAAM,QAAQ,QAAQ,OAAO,WAAW,OAAO;AAC/C,UAAM,OAAO,QAAQ,OAAO,QAAQ,gBAAgB,EAAE;AACtD,UAAM,QAAQ,GAAG,QAAQ,QAAQ,IAAI,MAAM,IAAI,aAAa,QAAQ,aAAa,mBAAmB,MAAM;AAE1G,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC1C,YAAM,KAAK,IAAI,UAAU,KAAK;AAC9B,WAAK,KAAK;AAEV,SAAG,SAAS,MAAM;AACf,WAAG,KAAK,KAAK,UAAU,EAAE,MAAM,SAAS,OAAO,QAAQ,YAAY,CAAC,CAAC;AAAA,MACxE;AAEA,SAAG,YAAY,CAAC,UAAU;AACvB,YAAI;AACD,gBAAM,MAAM,KAAK,MAAM,OAAO,MAAM,IAAI,CAAC;AAQzC,eAAK,cAAc,KAAK,SAAS,MAAM;AAAA,QAC1C,QAAQ;AAAA,QAER;AAAA,MACH;AAEA,SAAG,UAAU,MAAM;AAChB,cAAM,UAAU;AAChB,cAAM,gBAAgB,KAAK,iBAAiB;AAC5C,aAAK,gBAAgB,OAAO;AAC5B,YAAI,eAAe;AAChB,eAAK,aAAa,MAAM;AACxB,iBAAO,IAAI,MAAM,OAAO,CAAC;AAAA,QAC5B,OAAO;AACJ,eAAK,aAAa,QAAQ;AAC1B,eAAK,SAAS,UAAU,OAAO;AAAA,QAClC;AAAA,MACH;AAEA,SAAG,UAAU,MAAM;AAChB,aAAK,eAAe;AACpB,aAAK,eAAe;AACpB,aAAK,aAAa;AAClB,cAAM,eAAe,KAAK,iBAAiB;AAC3C,aAAK,gBAAgB,MAAM;AAC3B,aAAK,aAAa,MAAM;AACxB,YAAI,gBAAgB,CAAC,KAAK,uBAAuB;AAC9C,eAAK,oBAAoB,YAAY;AAAA,QACxC;AACA,aAAK,wBAAwB;AAAA,MAChC;AAAA,IACH,CAAC;AAAA,EACJ;AAAA,EAEQ,cACL,KAQA,cACA,aACK;AACL,QAAI,IAAI,SAAS,eAAe,IAAI,SAAS,SAAS;AACnD,YAAM,UAAU,IAAI,WAAW;AAC/B,UAAI,KAAK,iBAAiB,cAAc;AACrC,aAAK,gBAAgB,OAAO;AAC5B,oBAAY,IAAI,MAAM,OAAO,CAAC;AAAA,MACjC,OAAO;AACJ,aAAK,aAAa,QAAQ;AAC1B,aAAK,SAAS,UAAU,OAAO;AAAA,MAClC;AACA;AAAA,IACH;AAEA,QAAI,IAAI,SAAS,mBAAmB,KAAK,iBAAiB,cAAc;AACrE;AAAA,IACH;AAEA,QAAI,IAAI,SAAS,YAAY,IAAI,SAAS,0BAA0B;AACjE,WAAK,gBAAgB,WAAW;AAChC,WAAK,aAAa,OAAO;AACzB,WAAK,eAAe;AACpB,mBAAa;AACb;AAAA,IACH;AAEA,QAAI,IAAI,SAAS,WAAW,IAAI,OAAO;AACpC,UAAI,KAAK,uBAAuB,IAAI,KAAK,GAAG;AACzC;AAAA,MACH;AACA,WAAK,SAAS,cAAc,IAAI,KAAK;AACrC;AAAA,IACH;AAEA,QAAI,IAAI,SAAS,YAAY,IAAI,SAAS,yBAAyB;AAChE,WAAK,sBAAsB;AAC3B,WAAK,aAAa,SAAS;AAC3B;AAAA,IACH;AAEA,QAAI,IAAI,SAAS,YAAY,IAAI,SAAS,uBAAuB;AAC9D,WAAK,SAAS;AACd,WAAK,QAAQ;AACb,WAAK,sBAAsB;AAC3B,WAAK,eAAe;AACpB,WAAK,aAAa;AAClB,YAAM,aAAa,IAAI,OAAO,gBAAgB;AAC9C,YAAM,SAAS,OAAO,IAAI,OAAO,WAAW,WAAW,IAAI,MAAM,SAAS;AAC1E,UAAI,YAAY;AACb,aAAK,aAAa,MAAM;AACxB,aAAK,oBAAoB,UAAU,aAAa;AAAA,MACnD,OAAO;AACJ,aAAK,aAAa,OAAO;AAAA,MAC5B;AACA;AAAA,IACH;AAAA,EACH;AAAA;AAAA,EAGQ,uBAAuB,OAA8B;AAC1D,QAAI,MAAM,SAAS,YAAY,MAAM,SAAS,eAAe;AAC1D,aAAO;AAAA,IACV;AACA,QAAI,KAAK,OAAQ,QAAO;AAExB,UAAM,QAAQ,MAAM;AACpB,QAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAEhD,UAAM,OAAQ,MAAkC;AAChD,QAAI,OAAO,SAAS,YAAY,CAAC,KAAM,QAAO;AAE9C,QACG,CAAC,KAAK,wBACL,KAAK,cAAc,aAAa,KAAK,cAAc,SACrD;AACC,WAAK,sBAAsB;AAC3B,WAAK,aAAa,MAAM;AAAA,IAC3B;AACA,SAAK,KAAK,UAAU,qBAAqB,IAAI,CAAC;AAC9C,WAAO;AAAA,EACV;AAAA,EAEQ,iBAAuB;AAC5B,SAAK,eAAe;AACpB,SAAK,iBAAiB,YAAY,MAAM;AACrC,UAAI,KAAK,IAAI,eAAe,UAAU,MAAM;AACzC,aAAK,GAAG,KAAK,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC,CAAC;AAAA,MACrD;AAAA,IACH,GAAG,GAAM;AAAA,EACZ;AAAA,EAEQ,iBAAuB;AAC5B,QAAI,KAAK,gBAAgB;AACtB,oBAAc,KAAK,cAAc;AACjC,WAAK,iBAAiB;AAAA,IACzB;AAAA,EACH;AAAA,EAEQ,WAAW,MAAc,QAAiC,CAAC,GAAS;AACzE,QAAI,CAAC,KAAK,MAAM,KAAK,GAAG,eAAe,UAAU,MAAM;AACpD,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAChE;AACA,SAAK,GAAG,KAAK,KAAK,UAAU,EAAE,MAAM,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA,EAC/D;AAAA,EAEA,MAAM,UAAU,UAAiC;AAC9C,QAAI,CAAC,KAAK,MAAM,KAAK,GAAG,eAAe,UAAU,MAAM;AACpD,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAChE;AACA,QAAI,KAAK,iBAAiB,aAAa;AACpC,YAAM,IAAI,MAAM,+BAA+B;AAAA,IAClD;AACA,UAAM,KAAK,gBAAgB;AAC3B,SAAK,WAAW,uBAAuB,EAAE,WAAW,SAAS,CAAC;AAAA,EACjE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,MAAM,WAAW,eAAuB,aAAoC;AACzE,QAAI,CAAC,KAAK,MAAM,KAAK,GAAG,eAAe,UAAU,MAAM;AACpD,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAChE;AACA,QAAI,KAAK,iBAAiB,aAAa;AACpC,YAAM,IAAI,MAAM,+BAA+B;AAAA,IAClD;AACA,UAAM,KAAK,gBAAgB;AAC3B,SAAK,WAAW,wBAAwB;AAAA,MACrC,gBAAgB;AAAA,MAChB,cAAc;AAAA,IACjB,CAAC;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,SAAS,OAAsB;AAC5B,SAAK,QAAQ;AAAA,EAChB;AAAA,EAEA,UAAmB;AAChB,WAAO,KAAK;AAAA,EACf;AAAA,EAEA,SAAe;AACZ,QAAI;AACD,WAAK,WAAW,sBAAsB;AAAA,IACzC,QAAQ;AAAA,IAER;AACA,SAAK,SAAS;AACd,SAAK,sBAAsB;AAAA,EAC9B;AAAA,EAEA,YAAY,aAA2B;AACpC,SAAK,sBAAsB;AAC3B,SAAK,aAAa,SAAS;AAC3B,SAAK,WAAW,yBAAyB,EAAE,cAAc,YAAY,CAAC;AAAA,EACzE;AAAA,EAEA,QAAQ,QAAuB;AAC5B,SAAK,SAAS;AACd,SAAK,WAAW,sBAAsB,EAAE,SAAS,OAAO,CAAC;AACzD,QAAI,KAAK,cAAc,UAAU,KAAK,cAAc,UAAU;AAC3D,WAAK,aAAa,SAAS,WAAW,MAAM;AAAA,IAC/C;AACA,QAAI,QAAQ;AACT,WAAK,aAAa;AAAA,IACrB;AAAA,EACH;AAAA,EAEA,MAAM,kBAAiC;AACpC,QAAI,OAAO,WAAW,YAAa;AACnC,QAAI,CAAC,UAAU,cAAc,cAAc;AACxC,YAAM,IAAI,MAAM,0CAA0C;AAAA,IAC7D;AACA,QAAI,KAAK,UAAW;AAEpB,SAAK,cAAc,MAAM,UAAU,aAAa,aAAa;AAAA,MAC1D,OAAO;AAAA,QACJ,kBAAkB;AAAA,QAClB,kBAAkB;AAAA,QAClB,iBAAiB;AAAA,MACpB;AAAA,IACH,CAAC;AAED,UAAM,MACH,OAAO,gBACN,OAAmE;AACvE,QAAI,CAAC,IAAK,OAAM,IAAI,MAAM,yBAAyB;AAEnD,SAAK,eAAe,IAAI,IAAI;AAC5B,QAAI,KAAK,aAAa,UAAU,aAAa;AAC1C,YAAM,KAAK,aAAa,OAAO;AAAA,IAClC;AAEA,UAAM,SAAS,KAAK,aAAa,wBAAwB,KAAK,WAAW;AACzE,UAAM,YAAY,KAAK,aAAa,sBAAsB,MAAM,GAAG,CAAC;AACpE,SAAK,YAAY;AAEjB,cAAU,iBAAiB,CAAC,UAAU;AACnC,UAAI,CAAC,KAAK,MAAM,KAAK,GAAG,eAAe,UAAU,KAAM;AACvD,UAAI,KAAK,OAAQ;AACjB,UAAI,KAAK,MAAO;AAChB,UACG,KAAK,cAAc,aACnB,KAAK,cAAc,UACnB,KAAK,cAAc,UACpB;AACC;AAAA,MACH;AAEA,YAAM,QAAQ,MAAM,YAAY,eAAe,CAAC;AAChD,YAAM,OAAO,IAAI,aAAa,MAAM,MAAM;AAC1C,WAAK,IAAI,KAAK;AAEd,YAAM,aAAa,KAAK,cAAc,cAAc;AACpD,UAAI,MAAM;AACV,UAAI,eAAe,oBAAoB;AACpC,cAAM,QAAQ,aAAa;AAC3B,cAAM,SAAS,KAAK,MAAM,KAAK,SAAS,KAAK;AAC7C,cAAM,OAAO,IAAI,aAAa,MAAM;AACpC,iBAAS,IAAI,GAAG,IAAI,QAAQ,KAAK,GAAG;AACjC,eAAK,CAAC,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,CAAC;AAAA,QACvC;AACA,cAAM;AAAA,MACT;AAEA,WAAK,GAAG;AAAA,QACL,KAAK,UAAU;AAAA,UACZ,MAAM;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,YACJ,MAAM,qBAAqB,GAAG;AAAA,YAC9B,QAAQ;AAAA,YACR,aAAa;AAAA,UAChB;AAAA,QACH,CAAC;AAAA,MACJ;AAAA,IACH;AAEA,WAAO,QAAQ,SAAS;AACxB,cAAU,QAAQ,KAAK,aAAa,WAAW;AAC/C,SAAK,YAAY;AAAA,EACpB;AAAA,EAEA,iBAAuB;AACpB,SAAK,WAAW,WAAW;AAC3B,SAAK,YAAY;AACjB,eAAW,SAAS,KAAK,aAAa,UAAU,KAAK,CAAC,GAAG;AACtD,YAAM,KAAK;AAAA,IACd;AACA,SAAK,cAAc;AACnB,SAAK,YAAY;AAAA,EACpB;AAAA,EAEA,MAAc,UAAU,SAAsC;AAC3D,QAAI,OAAO,WAAW,YAAa;AAEnC,UAAM,MACH,OAAO,gBACN,OAAmE;AACvE,QAAI,CAAC,IAAK;AAEV,QAAI,CAAC,KAAK,gBAAgB,KAAK,aAAa,UAAU,UAAU;AAC7D,WAAK,eAAe,IAAI,IAAI;AAAA,IAC/B;AACA,QAAI,KAAK,aAAa,UAAU,aAAa;AAC1C,YAAM,KAAK,aAAa,OAAO;AAAA,IAClC;AAEA,UAAM,SAAS,KAAK,aAAa,aAAa,GAAG,QAAQ,QAAQ,kBAAkB;AACnF,WAAO,cAAc,IAAI,aAAa,OAAO,GAAG,CAAC;AAEjD,UAAM,SAAS,KAAK,aAAa,mBAAmB;AACpD,WAAO,SAAS;AAChB,WAAO,QAAQ,KAAK,aAAa,WAAW;AAE5C,UAAM,MAAM,KAAK,aAAa;AAC9B,UAAM,UAAU,KAAK,mBAAmB,MAAM,OAAO,MAAM,OAAO,KAAK;AACvE,WAAO,MAAM,OAAO;AACpB,SAAK,mBAAmB,UAAU,OAAO;AACzC,SAAK,gBAAgB,KAAK,MAAM;AAChC,WAAO,UAAU,MAAM;AACpB,YAAM,MAAM,KAAK,gBAAgB,QAAQ,MAAM;AAC/C,UAAI,OAAO,EAAG,MAAK,gBAAgB,OAAO,KAAK,CAAC;AAAA,IACnD;AAAA,EACH;AAAA,EAEQ,eAAqB;AAC1B,eAAW,OAAO,KAAK,iBAAiB;AACrC,UAAI;AACD,YAAI,KAAK;AAAA,MACZ,QAAQ;AAAA,MAER;AAAA,IACH;AACA,SAAK,kBAAkB,CAAC;AACxB,SAAK,mBAAmB;AAAA,EAC3B;AAAA,EAEA,MAAM,aAA4B;AAC/B,SAAK,wBAAwB;AAC7B,QACG,KAAK,cAAc,aACnB,KAAK,cAAc,UACnB,KAAK,cAAc,UACpB;AACC,WAAK,OAAO;AAAA,IACf;AACA,SAAK,eAAe;AACpB,SAAK,eAAe;AACpB,SAAK,aAAa;AAClB,QAAI,KAAK,IAAI;AACV,WAAK,GAAG,MAAM;AACd,WAAK,KAAK;AAAA,IACb;AACA,QAAI,KAAK,gBAAgB,KAAK,aAAa,UAAU,UAAU;AAC5D,YAAM,KAAK,aAAa,MAAM,EAAE,MAAM,MAAM,MAAS;AAAA,IACxD;AACA,SAAK,eAAe;AACpB,SAAK,SAAS;AACd,SAAK,QAAQ;AACb,SAAK,sBAAsB;AAC3B,SAAK,gBAAgB,MAAM;AAC3B,SAAK,aAAa,MAAM;AAAA,EAC3B;AACH;","names":["defaultClient"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { bm as ChannelMessagesUnavailableReason, k3 as Resource$1, R as ActivityEventLine, J as ActivityAggregateResponse, T as ActivityEventsResponse } from './chat-interaction-DLfC_h_P.cjs';
|
|
2
|
+
import { f0 as SortOrder } from './tasks-B-31JPuq.cjs';
|
|
3
3
|
|
|
4
4
|
interface MemberNotificationPreferences {
|
|
5
5
|
notifySms?: boolean;
|
|
@@ -13,6 +13,67 @@ interface MemberNotifyActionParams {
|
|
|
13
13
|
messageTemplate?: string | null;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Generated by Orval, grouped by scripts/types-codegen/group_generated.ts
|
|
18
|
+
* (group map from emit_openapi.py). Do not edit manually.
|
|
19
|
+
* Group: channel_message
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Which way the message crossed the wire, from the workspace's point of view.
|
|
24
|
+
*/
|
|
25
|
+
type ChannelMessageDirection = typeof ChannelMessageDirection[keyof typeof ChannelMessageDirection];
|
|
26
|
+
declare const ChannelMessageDirection: {
|
|
27
|
+
readonly inbound: "inbound";
|
|
28
|
+
readonly outbound: "outbound";
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* One message on this chat's channel that is not part of this chat.
|
|
32
|
+
*/
|
|
33
|
+
interface ChannelMessageResponse {
|
|
34
|
+
source: ChannelMessageSource;
|
|
35
|
+
/** The chat this message belongs to. Always null for `sms_record` items, which belong to no chat. Present only for chats the caller is authorized to read. */
|
|
36
|
+
sourceChatId?: number | null;
|
|
37
|
+
/** `messages.id`. Null for `sms_record` items. */
|
|
38
|
+
messageId?: number | null;
|
|
39
|
+
/** `sms_messages.id`. Always set for `sms_record` items; also set on an `other_chat` item whose Message is linked to a channel record. */
|
|
40
|
+
smsId?: number | null;
|
|
41
|
+
direction: ChannelMessageDirection;
|
|
42
|
+
/** Message content. PHI — do not log. */
|
|
43
|
+
body: string;
|
|
44
|
+
createdAt: string;
|
|
45
|
+
/** Agent or workflow name that sent an outbound message, when known. Never a member name. */
|
|
46
|
+
senderLabel?: string | null;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Where a ghost message came from.
|
|
50
|
+
*
|
|
51
|
+
* The two are not cosmetic variants: ``sms_record`` has no chat behind it at
|
|
52
|
+
* all (a one-off automated send, ``PersistencePolicy.SMS_ONLY``) and so can
|
|
53
|
+
* never be linked to, while ``other_chat`` is a real chat the caller has been
|
|
54
|
+
* authorized to read and can navigate to via ``sourceChatId``.
|
|
55
|
+
*/
|
|
56
|
+
type ChannelMessageSource = typeof ChannelMessageSource[keyof typeof ChannelMessageSource];
|
|
57
|
+
declare const ChannelMessageSource: {
|
|
58
|
+
readonly sms_record: "sms_record";
|
|
59
|
+
readonly other_chat: "other_chat";
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* A bounded page of ghost messages, oldest first.
|
|
63
|
+
*
|
|
64
|
+
* Not a ``PaginatedResponse``: there is no meaningful total here. The feed is
|
|
65
|
+
* two heterogeneous sources merged inside a time window, so counting it would
|
|
66
|
+
* mean running both queries unbounded — the exact thing the window exists to
|
|
67
|
+
* prevent. Callers page backwards with ``before``.
|
|
68
|
+
*/
|
|
69
|
+
interface ChannelMessagesResponse {
|
|
70
|
+
items: ChannelMessageResponse[];
|
|
71
|
+
/** More items exist inside the requested window, older than the earliest returned. Page backwards by passing that item's `createdAt` as `before`. */
|
|
72
|
+
hasMore: boolean;
|
|
73
|
+
/** Set only when `items` is empty *by construction* rather than because nothing was found: the chat's channel is not single-stream, it has no workspace phone number, or its member has no phone on file. Null on an ordinary empty result. */
|
|
74
|
+
reason?: ChannelMessagesUnavailableReason | null;
|
|
75
|
+
}
|
|
76
|
+
|
|
16
77
|
/** Legacy enum kept for callers using UPPER_SNAKE member names (e.g. ToolkitState.ENABLED). */
|
|
17
78
|
declare enum ToolkitState {
|
|
18
79
|
DISABLED = "disabled",
|
|
@@ -788,6 +849,158 @@ type PidsDisposition = (typeof PIDS_DISPOSITIONS)[number];
|
|
|
788
849
|
declare const PIDS_SOURCES: readonly ["scheduled", "backfill", "pre_llm"];
|
|
789
850
|
type PidsSource = (typeof PIDS_SOURCES)[number];
|
|
790
851
|
|
|
852
|
+
declare const MEMBER_RELATIONSHIP_KINDS: readonly [{
|
|
853
|
+
readonly kind: "parent_of";
|
|
854
|
+
readonly cardinality: "many_to_many";
|
|
855
|
+
readonly symmetric: false;
|
|
856
|
+
readonly grantsChartAccess: false;
|
|
857
|
+
readonly roles: [{
|
|
858
|
+
readonly role: "parent";
|
|
859
|
+
readonly label: "Parent";
|
|
860
|
+
readonly inverseLabel: "Child";
|
|
861
|
+
readonly description: "This person is a parent of the member. For a minor, also add Legal guardian if they should read the chart.";
|
|
862
|
+
}, {
|
|
863
|
+
readonly role: "child";
|
|
864
|
+
readonly label: "Child";
|
|
865
|
+
readonly inverseLabel: "Parent";
|
|
866
|
+
readonly description: "This person is a child of the member.";
|
|
867
|
+
}];
|
|
868
|
+
}, {
|
|
869
|
+
readonly kind: "sibling_of";
|
|
870
|
+
readonly cardinality: "many_to_many";
|
|
871
|
+
readonly symmetric: true;
|
|
872
|
+
readonly grantsChartAccess: false;
|
|
873
|
+
readonly roles: [{
|
|
874
|
+
readonly role: "sibling";
|
|
875
|
+
readonly label: "Sibling";
|
|
876
|
+
readonly inverseLabel: "Sibling";
|
|
877
|
+
readonly description: "These members are siblings.";
|
|
878
|
+
}];
|
|
879
|
+
}, {
|
|
880
|
+
readonly kind: "spouse_of";
|
|
881
|
+
readonly cardinality: "one_to_one";
|
|
882
|
+
readonly symmetric: true;
|
|
883
|
+
readonly grantsChartAccess: false;
|
|
884
|
+
readonly roles: [{
|
|
885
|
+
readonly role: "spouse";
|
|
886
|
+
readonly label: "Spouse";
|
|
887
|
+
readonly inverseLabel: "Spouse";
|
|
888
|
+
readonly description: "These members are spouses or partners.";
|
|
889
|
+
}];
|
|
890
|
+
}, {
|
|
891
|
+
readonly kind: "caregiver_of";
|
|
892
|
+
readonly cardinality: "many_to_many";
|
|
893
|
+
readonly symmetric: false;
|
|
894
|
+
readonly grantsChartAccess: false;
|
|
895
|
+
readonly roles: [{
|
|
896
|
+
readonly role: "caregiver";
|
|
897
|
+
readonly label: "Caregiver";
|
|
898
|
+
readonly inverseLabel: "Care recipient";
|
|
899
|
+
readonly description: "This person provides care for the member at home.";
|
|
900
|
+
}, {
|
|
901
|
+
readonly role: "care_recipient";
|
|
902
|
+
readonly label: "Care recipient";
|
|
903
|
+
readonly inverseLabel: "Caregiver";
|
|
904
|
+
readonly description: "This person receives care from the member.";
|
|
905
|
+
}];
|
|
906
|
+
}, {
|
|
907
|
+
readonly kind: "emergency_contact_of";
|
|
908
|
+
readonly cardinality: "many_to_many";
|
|
909
|
+
readonly symmetric: false;
|
|
910
|
+
readonly grantsChartAccess: false;
|
|
911
|
+
readonly roles: [{
|
|
912
|
+
readonly role: "emergency_contact";
|
|
913
|
+
readonly label: "Emergency contact";
|
|
914
|
+
readonly inverseLabel: "Emergency contact for";
|
|
915
|
+
readonly description: "This person is the member's emergency contact.";
|
|
916
|
+
}, {
|
|
917
|
+
readonly role: "emergency_contact_for";
|
|
918
|
+
readonly label: "Emergency contact for";
|
|
919
|
+
readonly inverseLabel: "Emergency contact";
|
|
920
|
+
readonly description: "The member is listed as this person's emergency contact.";
|
|
921
|
+
}];
|
|
922
|
+
}, {
|
|
923
|
+
readonly kind: "next_of_kin_of";
|
|
924
|
+
readonly cardinality: "one_per_target";
|
|
925
|
+
readonly symmetric: false;
|
|
926
|
+
readonly grantsChartAccess: false;
|
|
927
|
+
readonly roles: [{
|
|
928
|
+
readonly role: "next_of_kin";
|
|
929
|
+
readonly label: "Next of kin";
|
|
930
|
+
readonly inverseLabel: "Next of kin for";
|
|
931
|
+
readonly description: "This person is the member's designated next of kin. A member has at most one.";
|
|
932
|
+
}, {
|
|
933
|
+
readonly role: "next_of_kin_for";
|
|
934
|
+
readonly label: "Next of kin for";
|
|
935
|
+
readonly inverseLabel: "Next of kin";
|
|
936
|
+
readonly description: "The member is this person's designated next of kin.";
|
|
937
|
+
}];
|
|
938
|
+
}, {
|
|
939
|
+
readonly kind: "legal_guardian_of";
|
|
940
|
+
readonly cardinality: "many_to_many";
|
|
941
|
+
readonly symmetric: false;
|
|
942
|
+
readonly grantsChartAccess: true;
|
|
943
|
+
readonly roles: [{
|
|
944
|
+
readonly role: "legal_guardian";
|
|
945
|
+
readonly label: "Legal guardian";
|
|
946
|
+
readonly inverseLabel: "Legal guardian for";
|
|
947
|
+
readonly description: "Court-appointed guardian, or parent/person with parental responsibility for a minor.";
|
|
948
|
+
}, {
|
|
949
|
+
readonly role: "legal_guardian_for";
|
|
950
|
+
readonly label: "Legal guardian for";
|
|
951
|
+
readonly inverseLabel: "Legal guardian";
|
|
952
|
+
readonly description: "The member is this person's legal guardian.";
|
|
953
|
+
}];
|
|
954
|
+
}, {
|
|
955
|
+
readonly kind: "healthcare_agent_of";
|
|
956
|
+
readonly cardinality: "many_to_many";
|
|
957
|
+
readonly symmetric: false;
|
|
958
|
+
readonly grantsChartAccess: true;
|
|
959
|
+
readonly roles: [{
|
|
960
|
+
readonly role: "healthcare_agent";
|
|
961
|
+
readonly label: "Healthcare agent";
|
|
962
|
+
readonly inverseLabel: "Healthcare agent for";
|
|
963
|
+
readonly description: "Healthcare power of attorney or health proxy.";
|
|
964
|
+
}, {
|
|
965
|
+
readonly role: "healthcare_agent_for";
|
|
966
|
+
readonly label: "Healthcare agent for";
|
|
967
|
+
readonly inverseLabel: "Healthcare agent";
|
|
968
|
+
readonly description: "The member is this person's healthcare agent (healthcare power of attorney or health proxy).";
|
|
969
|
+
}];
|
|
970
|
+
}, {
|
|
971
|
+
readonly kind: "doctor_of";
|
|
972
|
+
readonly cardinality: "many_to_many";
|
|
973
|
+
readonly symmetric: false;
|
|
974
|
+
readonly grantsChartAccess: true;
|
|
975
|
+
readonly roles: [{
|
|
976
|
+
readonly role: "doctor";
|
|
977
|
+
readonly label: "Doctor";
|
|
978
|
+
readonly inverseLabel: "Patient";
|
|
979
|
+
readonly description: "This person is a treating clinician for the member.";
|
|
980
|
+
}, {
|
|
981
|
+
readonly role: "patient";
|
|
982
|
+
readonly label: "Patient";
|
|
983
|
+
readonly inverseLabel: "Doctor";
|
|
984
|
+
readonly description: "This person is a patient of the member.";
|
|
985
|
+
}];
|
|
986
|
+
}, {
|
|
987
|
+
readonly kind: "primary_contact_of";
|
|
988
|
+
readonly cardinality: "one_per_target";
|
|
989
|
+
readonly symmetric: false;
|
|
990
|
+
readonly grantsChartAccess: true;
|
|
991
|
+
readonly roles: [{
|
|
992
|
+
readonly role: "primary_contact";
|
|
993
|
+
readonly label: "Primary contact";
|
|
994
|
+
readonly inverseLabel: "Primary contact for";
|
|
995
|
+
readonly description: "This person is the organization's primary contact for the member. A member has at most one.";
|
|
996
|
+
}, {
|
|
997
|
+
readonly role: "primary_contact_for";
|
|
998
|
+
readonly label: "Primary contact for";
|
|
999
|
+
readonly inverseLabel: "Primary contact";
|
|
1000
|
+
readonly description: "The member is this person's assigned primary contact inside the organization.";
|
|
1001
|
+
}];
|
|
1002
|
+
}];
|
|
1003
|
+
|
|
791
1004
|
/**
|
|
792
1005
|
* Generated by Orval, grouped by scripts/types-codegen/group_generated.ts
|
|
793
1006
|
* (group map from emit_openapi.py). Do not edit manually.
|
|
@@ -1163,8 +1376,16 @@ declare function formatAppContextAsMarkdown(context: AppContext | undefined): st
|
|
|
1163
1376
|
* These are used as fallback defaults when creating new agents, assistants,
|
|
1164
1377
|
* and other entities that require a model selection.
|
|
1165
1378
|
*/
|
|
1166
|
-
/**
|
|
1379
|
+
/** Shared chat/inference fallback for Workflow and action settings. */
|
|
1167
1380
|
declare const DEFAULT_CHAT_MODEL = "aws:bedrock:claude-4-5-haiku";
|
|
1381
|
+
/** Chat model selected when creating an Agent. */
|
|
1382
|
+
declare const DEFAULT_AGENT_CHAT_MODEL = "openai:chat:gpt-5.6-luna";
|
|
1383
|
+
/** Default native realtime model and voice for newly created agents. */
|
|
1384
|
+
declare const DEFAULT_AGENT_REALTIME_MODEL = "xai:realtime:voice-agent-2";
|
|
1385
|
+
declare const DEFAULT_AGENT_REALTIME_MODEL_CONFIG: {
|
|
1386
|
+
ttsModel: string;
|
|
1387
|
+
voiceModel: string;
|
|
1388
|
+
};
|
|
1168
1389
|
/** Default inference model for custom voice stack. */
|
|
1169
1390
|
declare const DEFAULT_VOICE_INFERENCE_MODEL = "openai:chat:gpt-5.6-luna";
|
|
1170
1391
|
/** Default voice model for custom realtime configurations. */
|
|
@@ -1198,6 +1419,16 @@ declare function getResourceMetadata(resource: Resource$1): ResourceMetadata | u
|
|
|
1198
1419
|
*/
|
|
1199
1420
|
declare function getAllResourceSlugs(): Resource$1[];
|
|
1200
1421
|
|
|
1422
|
+
/** The typed per-type payload a leaf row carries, discriminated on `type`. */
|
|
1423
|
+
type ActivityDetail = NonNullable<ActivityEventLine['detail']>;
|
|
1424
|
+
/**
|
|
1425
|
+
* A feed query answers at one of two shapes, chosen by `grain`: aggregate lines
|
|
1426
|
+
* for `month`/`day`/`hour`, leaf rows for `event`. Discriminate on the presence
|
|
1427
|
+
* of `lines` versus `items` rather than on the request you sent — a drill query
|
|
1428
|
+
* you received may be at either grain.
|
|
1429
|
+
*/
|
|
1430
|
+
type ActivityFeedResponse = ActivityAggregateResponse | ActivityEventsResponse;
|
|
1431
|
+
|
|
1201
1432
|
/**
|
|
1202
1433
|
* Centralized OAuth and API scope management for TypeScript/JavaScript clients.
|
|
1203
1434
|
*
|
|
@@ -1321,7 +1552,7 @@ declare const SCOPES: {
|
|
|
1321
1552
|
readonly 'chats:write': {
|
|
1322
1553
|
readonly name: "chats:write";
|
|
1323
1554
|
readonly category: ScopeCategory.CHATS;
|
|
1324
|
-
readonly description: "
|
|
1555
|
+
readonly description: "Modify other members' conversations";
|
|
1325
1556
|
readonly implies: readonly ["chats:read"];
|
|
1326
1557
|
};
|
|
1327
1558
|
readonly 'chats:admin': {
|
|
@@ -1816,4 +2047,4 @@ interface RevisionState {
|
|
|
1816
2047
|
*/
|
|
1817
2048
|
declare function getRevisionState(workflow: RevisionStateWorkflow, revision?: RevisionStateRevision): RevisionState;
|
|
1818
2049
|
|
|
1819
|
-
export {
|
|
2050
|
+
export { type HealthSummary as $, type AIModelUsageResponse as A, type BillingStatistics as B, type CallEndSection as C, DEFAULT_AGENT_CHAT_MODEL as D, DEFAULT_CUSTOM_VOICE_MODEL as E, DEFAULT_VOICE_INFERENCE_MODEL as F, type DailyPhoneRowResponse as G, type DailySMSRowResponse as H, type DashboardMetrics as I, type DataRecordContext as J, type DeviceCacheFileEntry as K, type DeviceCacheManifestResponse as L, type EfficiencyMetricsResponse as M, type EmailCountsResponse as N, type EmailUsageResponse as O, type EngineTimeSeries as P, type ErrorCodeBucket as Q, type EventRuleMetrics as R, type ExportStatistics as S, type FeatureSyncStatus as T, type MetricSummary$1 as U, type VoiceMetricsResponse$1 as V, type WorkspaceHealthResponse$1 as W, type HealthCheckStatistics as X, type HealthDetailRequest as Y, type HealthMetricsRequest as Z, type HealthMetricsResponse as _, type AIUsageDetailsResponse as a, type WebhookMetrics as a$, type ImportStatistics as a0, MEMBER_RELATIONSHIP_KINDS as a1, type MemberNotificationPreferences as a2, type MemberNotifyActionParams as a3, type MemberUsageReport as a4, type MetricDataPoint as a5, type MetricSection as a6, type MetricSummary as a7, type MetricValue as a8, PIDS_DISPOSITIONS as a9, type SMSMessageUsageResponse as aA, type SMSPhoneNumberUsageResponse as aB, ScopeCategory as aC, ScopeManager as aD, type ScopeName as aE, type SiteContext as aF, type SkippedWorkspace as aG, type StatisticsSummary as aH, type SystemStatistics as aI, type TaskContext as aJ, type TaskCountResponse as aK, type TimeSeriesData as aL, type TimeSeriesDataPoint as aM, type ToolParameter as aN, type ToolkitContext as aO, ToolkitState as aP, type TopOffender as aQ, type TopResourceOverageResponse as aR, type TtsReconnectOutcomeEntry as aS, type TtsReconnectSection as aT, type UsageProductSummaryResponse as aU, type UsageReportResponse as aV, type UsageReportRequest as aW, type UsageStatistics as aX, type VoiceMetricsResponse as aY, type VoiceMetricsTimeRange as aZ, WIDGET_SLUGS as a_, PIDS_SOURCES as aa, type PageContext as ab, type PerformanceMetrics as ac, type PhoneCallPhoneNumberUsageResponse as ad, type PhoneCallUsageResponse as ae, type PhoneCommunicationDetailsResponse as af, type PhoneNumberUsage as ag, type PidsDisposition as ah, type PidsSource as ai, type ProductOverageConfigResponse as aj, type ProvisionPlanRequest as ak, type ProvisionPlanResponse as al, type RequestsSection as am, type RequestsStatusEntry as an, type ResourceMetadata as ao, type ResourceOverageResponse as ap, type ResourceUsageResponse as aq, type RevisionState as ar, type RevisionStateRevision as as, type RevisionStateWorkflow as at, type RoutineRunMetrics as au, type RoutineWorkflowAccessMissingRole as av, type RoutineWorkflowAccessWarning as aw, SCOPES as ax, SCOPE_PRESETS as ay, type SMSCommunicationDetailsResponse as az, type AcquisitionFunnelChannel as b, type WidgetSlug as b0, type WorkflowContext as b1, type WorkspaceHealthDetail as b2, type WorkspaceHealthResponse as b3, type WorkspaceHealthRow as b4, type WorkspaceRoutineWorkflowAccessResponse as b5, type WorkspaceUsageReportResponse as b6, formatAppContextAsMarkdown as b7, getAllResourceSlugs as b8, getResourceMetadata as b9, getRevisionState as ba, type ScopeDefinition as bb, type AcquisitionFunnelRecentSignup as c, type AcquisitionFunnelResponse as d, type ActivityDetail as e, type ActivityFeedResponse as f, type AdminDomainResponse as g, type AdminDomainSiteMapping as h, type AdminDomainWidgetMapping as i, type AdminPidsFindingResponse as j, type AnalyticsFilter as k, type AnalyticsQuery as l, type AppContext as m, type AttachedAddonInfo as n, type CallEndStatusEntry as o, type ChannelErrorBreakdown as p, type ChannelFailures as q, type ChannelMessageResponse as r, ChannelMessageDirection as s, ChannelMessageSource as t, type ChannelMessagesResponse as u, type CommunicationDetailsResponse as v, type CurrentPlanInfo as w, DEFAULT_AGENT_REALTIME_MODEL as x, DEFAULT_AGENT_REALTIME_MODEL_CONFIG as y, DEFAULT_CHAT_MODEL as z };
|