@gravity-rail/sdk 0.10.1 → 0.11.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 +7 -8
- package/dist/{chunk-TJT7A4CO.js → chunk-V7ZKEH4V.js} +1 -1
- package/dist/chunk-V7ZKEH4V.js.map +1 -0
- package/dist/{chunk-2BJG4GYM.js → chunk-YESHLUL7.js} +812 -180
- package/dist/chunk-YESHLUL7.js.map +1 -0
- package/dist/{client-BffM3KcA.d.cts → client-BCgXtr8p.d.cts} +2630 -1114
- package/dist/{client-DfjAYbt8.d.ts → client-Dh1l20Ue.d.ts} +2630 -1114
- package/dist/helpers.cjs +6 -18
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +2 -2
- package/dist/helpers.d.ts +2 -2
- package/dist/helpers.js +6 -18
- package/dist/helpers.js.map +1 -1
- package/dist/index.cjs +842 -268
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -21
- package/dist/index.d.ts +23 -21
- package/dist/index.js +51 -95
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +956 -258
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +443 -74
- package/dist/internal.d.ts +443 -74
- package/dist/internal.js +71 -2
- package/dist/internal.js.map +1 -1
- package/dist/realtime.cjs +42 -0
- package/dist/realtime.cjs.map +1 -1
- package/dist/realtime.d.cts +26 -0
- package/dist/realtime.d.ts +26 -0
- package/dist/realtime.js +42 -0
- package/dist/realtime.js.map +1 -1
- package/dist/{scopes-CmNKWrzb.d.ts → revision-state-BTF-MnbW.d.cts} +220 -18
- package/dist/{scopes-CjSvITpL.d.cts → revision-state-DOH1NMGb.d.ts} +220 -18
- package/dist/schemas.cjs +27 -110
- package/dist/schemas.cjs.map +1 -1
- package/dist/schemas.d.cts +48 -466
- package/dist/schemas.d.ts +48 -466
- package/dist/schemas.js +27 -107
- package/dist/schemas.js.map +1 -1
- package/dist/{inquiries-UcWNYo-Q.d.cts → tasks-C4aYbWVv.d.cts} +452 -141
- package/dist/{inquiries-UcWNYo-Q.d.ts → tasks-C4aYbWVv.d.ts} +452 -141
- package/package.json +1 -1
- package/dist/chunk-2BJG4GYM.js.map +0 -1
- package/dist/chunk-TJT7A4CO.js.map +0 -1
package/dist/realtime.js
CHANGED
|
@@ -251,6 +251,7 @@ var OperatorSessionClient = class {
|
|
|
251
251
|
playbackSources = [];
|
|
252
252
|
micActive = false;
|
|
253
253
|
onHold = false;
|
|
254
|
+
muted = false;
|
|
254
255
|
receivedRemoteAudio = false;
|
|
255
256
|
heartbeatTimer = null;
|
|
256
257
|
sessionCloseNotified = false;
|
|
@@ -360,6 +361,7 @@ var OperatorSessionClient = class {
|
|
|
360
361
|
}
|
|
361
362
|
if (msg.type === "CUSTOM" && msg.name === "operator.call.ended") {
|
|
362
363
|
this.onHold = false;
|
|
364
|
+
this.muted = false;
|
|
363
365
|
this.receivedRemoteAudio = false;
|
|
364
366
|
this.stopMicrophone();
|
|
365
367
|
this.stopPlayback();
|
|
@@ -421,6 +423,44 @@ var OperatorSessionClient = class {
|
|
|
421
423
|
await this.startMicrophone();
|
|
422
424
|
this.sendCustom("operator.call.place", { member_id: memberId });
|
|
423
425
|
}
|
|
426
|
+
/**
|
|
427
|
+
* Answer an inbound operator call the caller has already been routed to.
|
|
428
|
+
*
|
|
429
|
+
* Symmetric with {@link placeCall} but for the inbound direction: instead of
|
|
430
|
+
* dialing an outbound leg it tells the backend to bridge the already-ringing
|
|
431
|
+
* caller to this operator session. The backend (see
|
|
432
|
+
* `lib/operator_session_handler.py::_answer_inbound_call`) registers the
|
|
433
|
+
* audio bridge, marks the ring state `answered`, and replies with
|
|
434
|
+
* `operator.call.started` — after which callee PCM arrives as `voice.audio`
|
|
435
|
+
* events (played via {@link playPcm16}) and mic audio streams the other way,
|
|
436
|
+
* exactly like an outbound call.
|
|
437
|
+
*
|
|
438
|
+
* Must be called once the session is ready (state `connected`). Starts the
|
|
439
|
+
* microphone before sending so the operator's audio is flowing the instant
|
|
440
|
+
* the bridge registers.
|
|
441
|
+
*/
|
|
442
|
+
async answerCall(interactionId) {
|
|
443
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
444
|
+
throw new Error("Operator session WebSocket is not connected");
|
|
445
|
+
}
|
|
446
|
+
if (this.sessionState !== "connected") {
|
|
447
|
+
throw new Error("Operator session is not ready");
|
|
448
|
+
}
|
|
449
|
+
await this.startMicrophone();
|
|
450
|
+
this.sendCustom("operator.call.answer", { interaction_id: interactionId });
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Mute or unmute the operator's outbound microphone. Muting gates mic frames
|
|
454
|
+
* locally (the caller stops hearing the operator) while callee audio keeps
|
|
455
|
+
* playing — distinct from {@link setHold}, which is a two-way pause the
|
|
456
|
+
* backend is told about. Purely client-side; no server round-trip.
|
|
457
|
+
*/
|
|
458
|
+
setMuted(muted) {
|
|
459
|
+
this.muted = muted;
|
|
460
|
+
}
|
|
461
|
+
isMuted() {
|
|
462
|
+
return this.muted;
|
|
463
|
+
}
|
|
424
464
|
hangUp() {
|
|
425
465
|
try {
|
|
426
466
|
this.sendCustom("operator.call.hangup");
|
|
@@ -469,6 +509,7 @@ var OperatorSessionClient = class {
|
|
|
469
509
|
processor.onaudioprocess = (event) => {
|
|
470
510
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
|
|
471
511
|
if (this.onHold) return;
|
|
512
|
+
if (this.muted) return;
|
|
472
513
|
if (this.callPhase !== "dialing" && this.callPhase !== "live" && this.callPhase !== "onHold") {
|
|
473
514
|
return;
|
|
474
515
|
}
|
|
@@ -563,6 +604,7 @@ var OperatorSessionClient = class {
|
|
|
563
604
|
}
|
|
564
605
|
this.audioContext = null;
|
|
565
606
|
this.onHold = false;
|
|
607
|
+
this.muted = false;
|
|
566
608
|
this.receivedRemoteAudio = false;
|
|
567
609
|
this.setSessionState("idle");
|
|
568
610
|
this.setCallPhase("idle");
|
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 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.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 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 (\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.receivedRemoteAudio = false;\n this.setSessionState('idle');\n this.setCallPhase('idle');\n }\n}\n"],"mappings":"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","names":[]}
|
|
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":"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","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { ev as SortOrder } from './tasks-C4aYbWVv.cjs';
|
|
2
|
+
import { mz as WorkspaceFeature, hS as Resource$1, hk as ProductType } from './client-BCgXtr8p.cjs';
|
|
3
3
|
|
|
4
4
|
interface MemberNotificationPreferences {
|
|
5
5
|
notifySms?: boolean;
|
|
@@ -29,8 +29,8 @@ interface ToolParameter {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* Generated by Orval
|
|
33
|
-
*
|
|
32
|
+
* Generated by Orval, grouped by scripts/types-codegen/group_generated.ts
|
|
33
|
+
* (group map from emit_openapi.py). Do not edit manually.
|
|
34
34
|
* Group: workspace_health
|
|
35
35
|
*/
|
|
36
36
|
/**
|
|
@@ -124,6 +124,48 @@ interface ProvisionPlanRequest {
|
|
|
124
124
|
subscriptionId?: number;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Generated by Orval, grouped by scripts/types-codegen/group_generated.ts
|
|
129
|
+
* (group map from emit_openapi.py). Do not edit manually.
|
|
130
|
+
* Group: domain
|
|
131
|
+
*/
|
|
132
|
+
/**
|
|
133
|
+
* A registered domain and every capability mapped onto it, org-agnostic.
|
|
134
|
+
*/
|
|
135
|
+
interface AdminDomainResponse {
|
|
136
|
+
uuid: string;
|
|
137
|
+
domain: string;
|
|
138
|
+
organizationId: number;
|
|
139
|
+
organizationName: string;
|
|
140
|
+
ownershipStatus: string;
|
|
141
|
+
ownershipVerifiedAt?: string | null;
|
|
142
|
+
emailConfigured: boolean;
|
|
143
|
+
emailStatus?: string | null;
|
|
144
|
+
siteConfigured: boolean;
|
|
145
|
+
siteMappings: AdminDomainSiteMapping[];
|
|
146
|
+
widgetMappings: AdminDomainWidgetMapping[];
|
|
147
|
+
createdAt: string;
|
|
148
|
+
updatedAt: string;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* One site a domain routes to (from ``SiteMapping``).
|
|
152
|
+
*/
|
|
153
|
+
interface AdminDomainSiteMapping {
|
|
154
|
+
siteSlug?: string | null;
|
|
155
|
+
workspaceName: string;
|
|
156
|
+
workspaceUuid: string;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* One workspace's widget enablement on a domain (workspace_widget_domains).
|
|
160
|
+
*/
|
|
161
|
+
interface AdminDomainWidgetMapping {
|
|
162
|
+
workspaceName: string;
|
|
163
|
+
workspaceUuid: string;
|
|
164
|
+
widgets: string[];
|
|
165
|
+
allowSubdomains: boolean;
|
|
166
|
+
port?: number | null;
|
|
167
|
+
}
|
|
168
|
+
|
|
127
169
|
interface TaskCountResponse {
|
|
128
170
|
taskCount: number;
|
|
129
171
|
}
|
|
@@ -247,8 +289,8 @@ interface SystemStatistics {
|
|
|
247
289
|
}
|
|
248
290
|
|
|
249
291
|
/**
|
|
250
|
-
* Generated by Orval
|
|
251
|
-
*
|
|
292
|
+
* Generated by Orval, grouped by scripts/types-codegen/group_generated.ts
|
|
293
|
+
* (group map from emit_openapi.py). Do not edit manually.
|
|
252
294
|
* Group: usage
|
|
253
295
|
*/
|
|
254
296
|
/**
|
|
@@ -288,7 +330,7 @@ interface CommunicationDetailsResponse {
|
|
|
288
330
|
interface DailyPhoneRowResponse {
|
|
289
331
|
date: string;
|
|
290
332
|
phoneNumberId: number;
|
|
291
|
-
phoneNumber: string;
|
|
333
|
+
phoneNumber: string | null;
|
|
292
334
|
phoneName: string | null;
|
|
293
335
|
inbound: number;
|
|
294
336
|
outbound: number;
|
|
@@ -300,7 +342,7 @@ interface DailyPhoneRowResponse {
|
|
|
300
342
|
interface DailySMSRowResponse {
|
|
301
343
|
date: string;
|
|
302
344
|
phoneNumberId: number;
|
|
303
|
-
phoneNumber: string;
|
|
345
|
+
phoneNumber: string | null;
|
|
304
346
|
phoneName: string | null;
|
|
305
347
|
inbound: number;
|
|
306
348
|
outbound: number;
|
|
@@ -327,7 +369,7 @@ interface EmailUsageResponse {
|
|
|
327
369
|
*/
|
|
328
370
|
interface PhoneCallPhoneNumberUsageResponse {
|
|
329
371
|
phoneNumberId: number;
|
|
330
|
-
phoneNumber: string;
|
|
372
|
+
phoneNumber: string | null;
|
|
331
373
|
phoneName: string | null;
|
|
332
374
|
inbound: number;
|
|
333
375
|
outbound: number;
|
|
@@ -431,7 +473,7 @@ interface SMSMessageUsageResponse {
|
|
|
431
473
|
*/
|
|
432
474
|
interface SMSPhoneNumberUsageResponse {
|
|
433
475
|
phoneNumberId: number;
|
|
434
|
-
phoneNumber: string;
|
|
476
|
+
phoneNumber: string | null;
|
|
435
477
|
phoneName: string | null;
|
|
436
478
|
inbound: number;
|
|
437
479
|
outbound: number;
|
|
@@ -491,7 +533,8 @@ interface WorkspaceUsageReportResponse {
|
|
|
491
533
|
|
|
492
534
|
interface PhoneNumberUsage {
|
|
493
535
|
phoneNumberId: number;
|
|
494
|
-
|
|
536
|
+
/** Matches PhoneNumber.number (nullable). */
|
|
537
|
+
phoneNumber: string | null;
|
|
495
538
|
phoneName: string | null;
|
|
496
539
|
inbound: number;
|
|
497
540
|
outbound: number;
|
|
@@ -511,8 +554,8 @@ interface MemberUsageReport {
|
|
|
511
554
|
}
|
|
512
555
|
|
|
513
556
|
/**
|
|
514
|
-
* Generated by Orval
|
|
515
|
-
*
|
|
557
|
+
* Generated by Orval, grouped by scripts/types-codegen/group_generated.ts
|
|
558
|
+
* (group map from emit_openapi.py). Do not edit manually.
|
|
516
559
|
* Group: voice_metrics
|
|
517
560
|
*/
|
|
518
561
|
interface CallEndSection$1 {
|
|
@@ -550,7 +593,7 @@ interface MetricSummary$1 {
|
|
|
550
593
|
p50?: number;
|
|
551
594
|
p90?: number;
|
|
552
595
|
p99?: number;
|
|
553
|
-
|
|
596
|
+
hasDistribution?: boolean;
|
|
554
597
|
}
|
|
555
598
|
interface RequestsSection$1 {
|
|
556
599
|
statuses: RequestsStatusEntry$1[];
|
|
@@ -663,8 +706,8 @@ declare const PIDS_SOURCES: readonly ["scheduled", "backfill", "pre_llm"];
|
|
|
663
706
|
type PidsSource = (typeof PIDS_SOURCES)[number];
|
|
664
707
|
|
|
665
708
|
/**
|
|
666
|
-
* Generated by Orval
|
|
667
|
-
*
|
|
709
|
+
* Generated by Orval, grouped by scripts/types-codegen/group_generated.ts
|
|
710
|
+
* (group map from emit_openapi.py). Do not edit manually.
|
|
668
711
|
* Group: health_dashboard
|
|
669
712
|
*/
|
|
670
713
|
/**
|
|
@@ -719,9 +762,10 @@ interface HealthSummary {
|
|
|
719
762
|
totalWebhookVolume?: number;
|
|
720
763
|
totalSmsResponded?: number;
|
|
721
764
|
totalSmsOutboundChats?: number;
|
|
722
|
-
totalMemberChats?: number;
|
|
723
765
|
totalNeedsResponse?: number;
|
|
724
766
|
totalAccessGapWarnings?: number;
|
|
767
|
+
totalRoutineWorkflowAccessWarnings?: number;
|
|
768
|
+
totalOpenCriticalActionIncidents?: number;
|
|
725
769
|
}
|
|
726
770
|
/**
|
|
727
771
|
* A workspace that was skipped during metrics collection.
|
|
@@ -754,6 +798,47 @@ interface WorkspaceHealthRow {
|
|
|
754
798
|
memberChats?: number;
|
|
755
799
|
needsResponse?: number;
|
|
756
800
|
accessGapWarnings?: number;
|
|
801
|
+
routineWorkflowAccessWarnings?: number;
|
|
802
|
+
openCriticalActionIncidents?: number;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* A role in a Routine's audience that lacks Workflow USE.
|
|
807
|
+
*
|
|
808
|
+
* IDs + names are safe for admins (who already see role names); logs/breadcrumbs
|
|
809
|
+
* must use IDs only.
|
|
810
|
+
*/
|
|
811
|
+
interface RoutineWorkflowAccessMissingRole {
|
|
812
|
+
id: number;
|
|
813
|
+
name: string;
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* One Routine → Workflow reference whose audience is missing Workflow USE.
|
|
817
|
+
*
|
|
818
|
+
* PHI-safe: Routine / EventRule / Workflow / role IDs and names only — no
|
|
819
|
+
* Member-level data (names, phones, emails). ``actionType`` is either an
|
|
820
|
+
* EventRule action type (e.g. ``"sms:send"``) or ``"routine:workflow"`` for
|
|
821
|
+
* the ``Routine.workflow_id`` background-run path.
|
|
822
|
+
*/
|
|
823
|
+
interface RoutineWorkflowAccessWarning {
|
|
824
|
+
routineId: number;
|
|
825
|
+
routineName?: string | null;
|
|
826
|
+
eventRuleId?: number | null;
|
|
827
|
+
actionType: string;
|
|
828
|
+
workflowId: number;
|
|
829
|
+
workflowUuid: string;
|
|
830
|
+
workflowName: string;
|
|
831
|
+
targetKind: string;
|
|
832
|
+
missingRoles: RoutineWorkflowAccessMissingRole[];
|
|
833
|
+
audienceRoleCount: number;
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Routine Workflow-access warning detail for a single workspace.
|
|
837
|
+
*/
|
|
838
|
+
interface WorkspaceRoutineWorkflowAccessResponse {
|
|
839
|
+
workspaceUuid: string;
|
|
840
|
+
warnings: RoutineWorkflowAccessWarning[];
|
|
841
|
+
totalWarnings: number;
|
|
757
842
|
}
|
|
758
843
|
|
|
759
844
|
interface HealthMetricsRequest {
|
|
@@ -788,6 +873,79 @@ interface HealthDetailRequest {
|
|
|
788
873
|
hours?: number;
|
|
789
874
|
}
|
|
790
875
|
|
|
876
|
+
/**
|
|
877
|
+
* Generated by Orval, grouped by scripts/types-codegen/group_generated.ts
|
|
878
|
+
* (group map from emit_openapi.py). Do not edit manually.
|
|
879
|
+
* Group: acquisition_funnel
|
|
880
|
+
*/
|
|
881
|
+
/**
|
|
882
|
+
* Signups within the window grouped by first-touch acquisition source.
|
|
883
|
+
*
|
|
884
|
+
* ``source`` matches ``BookOfBusinessAcquisitionDistribution.source``
|
|
885
|
+
* semantics (``utm_source`` or ``"direct"``) so the two rollups join.
|
|
886
|
+
* Only accounts WITH an acquisition record are grouped here; record-less
|
|
887
|
+
* accounts are counted in ``unattributedSignupCount`` instead.
|
|
888
|
+
*/
|
|
889
|
+
interface AcquisitionFunnelChannel {
|
|
890
|
+
source: string;
|
|
891
|
+
signupCount: number;
|
|
892
|
+
latestSignupAt: string;
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* One attributed signup, newest-first — the low-volume legibility core.
|
|
896
|
+
*
|
|
897
|
+
* At a handful of signups a month the per-signup list IS the report. PII-free
|
|
898
|
+
* by construction: account id only (never email/name), and the acquisition
|
|
899
|
+
* fields are the allowlisted, query-stripped marketing values
|
|
900
|
+
* (``lib.services.acquisition.sanitize_acquisition``).
|
|
901
|
+
*/
|
|
902
|
+
interface AcquisitionFunnelRecentSignup {
|
|
903
|
+
accountId: number;
|
|
904
|
+
createdAt: string;
|
|
905
|
+
source: string;
|
|
906
|
+
utmMedium?: string | null;
|
|
907
|
+
utmCampaign?: string | null;
|
|
908
|
+
referrer?: string | null;
|
|
909
|
+
landingPage?: string | null;
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Channel -> signup rollup of ``Account.acquisition`` (superuser only).
|
|
913
|
+
*/
|
|
914
|
+
interface AcquisitionFunnelResponse {
|
|
915
|
+
generatedAt: string;
|
|
916
|
+
queryVersion: string;
|
|
917
|
+
windowDays: number;
|
|
918
|
+
windowStart: string;
|
|
919
|
+
captureLiveSince: string | null;
|
|
920
|
+
signupCount: number;
|
|
921
|
+
attributedSignupCount: number;
|
|
922
|
+
unattributedSignupCount: number;
|
|
923
|
+
channels: AcquisitionFunnelChannel[];
|
|
924
|
+
recentAttributedSignups: AcquisitionFunnelRecentSignup[];
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/**
|
|
928
|
+
* Widget-domain configuration types.
|
|
929
|
+
*
|
|
930
|
+
* Server response/request shapes are generated from Pydantic via
|
|
931
|
+
* emit_openapi.py → Orval into ./generated/workspace_widget_domain. This thin
|
|
932
|
+
* layer re-exports them under stable public names and adds the client-only
|
|
933
|
+
* widget-slug registry the UI iterates over.
|
|
934
|
+
*
|
|
935
|
+
* The candidate-domain list served by GET /w/{wid}/domains/for-widget reuses
|
|
936
|
+
* `WorkspaceAvailableDomainsResponse` (see ./organization-types).
|
|
937
|
+
*/
|
|
938
|
+
|
|
939
|
+
/**
|
|
940
|
+
* Canonical embeddable-widget slugs. Mirrors the backend `WidgetSlug` enum
|
|
941
|
+
* (`lib/models/glb/workspace_widget_domain_models.py`); kept as a client-side
|
|
942
|
+
* const so UI can render one toggle per slug without a round-trip. Keep in sync
|
|
943
|
+
* with the backend registry — it is the source of truth the config API and the
|
|
944
|
+
* origin gate both validate against.
|
|
945
|
+
*/
|
|
946
|
+
declare const WIDGET_SLUGS: readonly ["chat", "operator", "operator-tools", "chat-app"];
|
|
947
|
+
type WidgetSlug = (typeof WIDGET_SLUGS)[number];
|
|
948
|
+
|
|
791
949
|
/**
|
|
792
950
|
* App context types - shared between client and server
|
|
793
951
|
*
|
|
@@ -1649,4 +1807,48 @@ declare const SCOPE_PRESETS: {
|
|
|
1649
1807
|
ALL: ScopeName[];
|
|
1650
1808
|
};
|
|
1651
1809
|
|
|
1652
|
-
|
|
1810
|
+
/**
|
|
1811
|
+
* Derived revision state — the single place derived booleans about a workflow
|
|
1812
|
+
* revision live. The API never puts these on the wire (they can contradict
|
|
1813
|
+
* their inputs; the facts they derive from can't). Every consumer — web app,
|
|
1814
|
+
* CLI, MCP — computes them here from the workflow root's pointer columns plus
|
|
1815
|
+
* the revision's own facts.
|
|
1816
|
+
*
|
|
1817
|
+
* See docs/features/conversations/workflows/versioning/api-shape.md.
|
|
1818
|
+
*/
|
|
1819
|
+
/** The two facts a revision derivation needs from the stable workflow root. */
|
|
1820
|
+
interface RevisionStateWorkflow {
|
|
1821
|
+
activeRevisionId: number;
|
|
1822
|
+
draftRevisionId: number;
|
|
1823
|
+
/** Fact behind "has unpublished changes"; null after a fresh clone/publish. */
|
|
1824
|
+
draftModifiedAt?: string | null;
|
|
1825
|
+
}
|
|
1826
|
+
/** The two facts a derivation needs from the revision itself, when one is in hand. */
|
|
1827
|
+
interface RevisionStateRevision {
|
|
1828
|
+
id: number;
|
|
1829
|
+
/** Present on published revisions, null/absent on the draft. */
|
|
1830
|
+
publishedAt?: string | null;
|
|
1831
|
+
}
|
|
1832
|
+
interface RevisionState {
|
|
1833
|
+
/** This revision is the workflow's active (live-traffic) revision. */
|
|
1834
|
+
isActive: boolean;
|
|
1835
|
+
/** This revision is the workflow's mutable draft. */
|
|
1836
|
+
isDraft: boolean;
|
|
1837
|
+
/** active === draft: live traffic reads the editor's mutable draft. */
|
|
1838
|
+
draftIsLive: boolean;
|
|
1839
|
+
/** The draft has been edited since the last publish (or since creation). */
|
|
1840
|
+
hasUnpublishedChanges: boolean;
|
|
1841
|
+
/** 'published' once the revision has a publish timestamp, else 'draft'. */
|
|
1842
|
+
state: 'draft' | 'published';
|
|
1843
|
+
}
|
|
1844
|
+
/**
|
|
1845
|
+
* Derive a revision's state from the workflow root pointers and (optionally)
|
|
1846
|
+
* the revision in hand. Pure — no I/O, no allocation beyond the result.
|
|
1847
|
+
*
|
|
1848
|
+
* When `revision` is omitted, per-revision facts default to false / 'draft'
|
|
1849
|
+
* (there is no revision to compare), while the workflow-level derivations
|
|
1850
|
+
* (`draftIsLive`, `hasUnpublishedChanges`) are still computed.
|
|
1851
|
+
*/
|
|
1852
|
+
declare function getRevisionState(workflow: RevisionStateWorkflow, revision?: RevisionStateRevision): RevisionState;
|
|
1853
|
+
|
|
1854
|
+
export { type HealthMetricsResponse as $, type AIModelUsageResponse as A, type BaseChatModelConfig as B, type CallEndSection as C, DEFAULT_CHAT_MODEL as D, DEFAULT_THINKING_SPEECH_PHRASES 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 EfficiencyMetricsResponse as K, type EmailCountsResponse as L, type EmailUsageResponse as M, type EngineTimeSeries as N, type ErrorCodeBucket as O, type EventRuleMetrics as P, type ExportStatistics as Q, type FeatureSyncStatus as R, type GeminiChatModelConfig as S, type GeminiRealtimeModelConfig as T, type GeminiThinkingLevel as U, type MetricSummary$1 as V, type VoiceMetricsResponse$1 as W, type WorkspaceHealthResponse$1 as X, type HealthCheckStatistics as Y, type HealthDetailRequest as Z, type HealthMetricsRequest as _, type AIUsageDetailsResponse as a, type ToolParameter as a$, type HealthSummary as a0, type ImportStatistics as a1, type InterruptionSound as a2, type MemberNotificationPreferences as a3, type MemberNotifyActionParams as a4, type MemberUsageReport as a5, type MetricDataPoint as a6, type MetricSection as a7, type MetricSummary as a8, type MetricValue as a9, type ResourceScope as aA, type ResourceUsageResponse as aB, type RevisionState as aC, type RevisionStateRevision as aD, type RevisionStateWorkflow as aE, type RoutineWorkflowAccessMissingRole as aF, type RoutineWorkflowAccessWarning as aG, SCOPES as aH, SCOPE_PRESETS as aI, type SMSCommunicationDetailsResponse as aJ, type SMSMessageUsageResponse as aK, type SMSPhoneNumberUsageResponse as aL, ScopeCategory as aM, ScopeManager as aN, type ScopeName as aO, type SiteContext as aP, type SkippedWorkspace as aQ, type StatisticsSummary as aR, type SystemStatistics as aS, type TTSModel as aT, type TTSPreviewProvider as aU, TTS_PREVIEW_PROVIDERS as aV, type TaskContext as aW, type TaskCountResponse as aX, type ThinkingSound as aY, type TimeSeriesData as aZ, type TimeSeriesDataPoint as a_, type OpenAIChatModelConfig as aa, type OpenAIRealtimeModelConfig as ab, type OpenAIReasoningEffort as ac, type OpenAISemanticVADEagerness as ad, type OpenAISemanticVADTurnDetection as ae, type OpenAIServerVADTurnDetection as af, type OpenAITurnDetection as ag, type OpenAITurnDetectionType as ah, PIDS_DISPOSITIONS as ai, PIDS_SOURCES as aj, type PageContext as ak, type PerformanceMetrics as al, type PhoneCallPhoneNumberUsageResponse as am, type PhoneCallUsageResponse as an, type PhoneCommunicationDetailsResponse as ao, type PhoneNumberUsage as ap, type PidsDisposition as aq, type PidsSource as ar, type ProductOverageConfigResponse as as, type ProvisionPlanRequest as at, type ProvisionPlanResponse as au, type RealtimeModelConfig as av, type RequestsSection as aw, type RequestsStatusEntry as ax, type ResourceMetadata as ay, type ResourceOverageResponse as az, type AWSRealtimeModelConfig as b, type ToolkitContext as b0, ToolkitState as b1, type TopOffender as b2, type TopResourceOverageResponse as b3, type TtsReconnectOutcomeEntry as b4, type TtsReconnectSection as b5, type UsageProductSummaryResponse as b6, type UsageReportResponse as b7, type UsageReportRequest as b8, type UsageStatistics as b9, type VoiceMetricsResponse as ba, type VoiceMetricsTimeRange as bb, WIDGET_SLUGS as bc, type WebhookMetrics as bd, type WidgetSlug as be, type WorkflowContext as bf, type WorkspaceHealthDetail as bg, type WorkspaceHealthResponse as bh, type WorkspaceHealthRow as bi, type WorkspaceRoutineWorkflowAccessResponse as bj, type WorkspaceUsageReportResponse as bk, type XAIChatModelConfig as bl, type XAIRealtimeModelConfig as bm, type XAIReasoningEffort as bn, canPreviewVoice as bo, formatAppContextAsMarkdown as bp, getAllResourceSlugs as bq, getApplicableResourceSlugs as br, getApplicableResources as bs, getResourceMetadata as bt, getRevisionState as bu, getScopeFromProductType as bv, getTTSProvider as bw, getVoiceModelId as bx, type ScopeDefinition as by, type AcquisitionFunnelChannel as c, type AcquisitionFunnelRecentSignup as d, type AcquisitionFunnelResponse as e, type AdminDomainResponse as f, type AdminDomainSiteMapping as g, type AdminDomainWidgetMapping as h, type AdminPidsFindingResponse as i, type AnalyticsFilter as j, type AnalyticsQuery as k, type AppContext as l, type AttachedAddonInfo as m, type BaseRealtimeModelConfig as n, type BillingStatistics as o, type CallEndStatusEntry as p, type ChangeWorkspaceProductRequest as q, type ChangeWorkspaceProductResponse as r, type ChannelErrorBreakdown as s, type ChannelFailures as t, type ChatModelConfig as u, type ClaudeChatModelConfig as v, type CommunicationDetailsResponse as w, type CurrentPlanInfo as x, type CustomRealtimeModelConfig as y, DEFAULT_CUSTOM_VOICE_MODEL as z };
|