@gravity-rail/sdk 0.36.0 → 0.40.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 +44 -21
- 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/{chat-interaction-Ck1oMOr5.d.ts → chat-interaction-BB3ap8_u.d.ts} +8920 -6212
- package/dist/{chat-interaction-DqQu8Ad1.d.cts → chat-interaction-Bj1pciwo.d.cts} +8920 -6212
- package/dist/{chunk-P2E6LU7I.js → chunk-67QXTQVL.js} +1 -26
- package/dist/chunk-67QXTQVL.js.map +1 -0
- package/dist/chunk-6KPGCPGW.js +48 -0
- package/dist/chunk-6KPGCPGW.js.map +1 -0
- package/dist/{chunk-HRUMZ46V.js → chunk-72SWNLVD.js} +2 -1
- package/dist/chunk-72SWNLVD.js.map +1 -0
- package/dist/{chunk-R3C63RZJ.js → chunk-ZTUOB4V2.js} +1278 -218
- package/dist/chunk-ZTUOB4V2.js.map +1 -0
- package/dist/helpers.cjs +47 -0
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +4 -3
- package/dist/helpers.d.ts +4 -3
- package/dist/helpers.js +7 -1
- package/dist/helpers.js.map +1 -1
- package/dist/index.cjs +1389 -350
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -35
- package/dist/index.d.ts +13 -35
- package/dist/index.js +112 -122
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +1303 -252
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +338 -55
- package/dist/internal.d.ts +338 -55
- package/dist/internal.js +68 -21
- package/dist/internal.js.map +1 -1
- package/dist/member-import-D8fZr2Pd.d.cts +46 -0
- package/dist/member-import-YNk9MlEl.d.ts +46 -0
- package/dist/realtime.cjs +91 -20
- package/dist/realtime.cjs.map +1 -1
- package/dist/realtime.d.cts +34 -9
- package/dist/realtime.d.ts +34 -9
- package/dist/realtime.js +90 -20
- package/dist/realtime.js.map +1 -1
- package/dist/{revision-state-Tn2V0-zt.d.cts → revision-state-BaJTq3Or.d.ts} +271 -55
- package/dist/{revision-state-V5RIhte0.d.ts → revision-state-CL11FA-T.d.cts} +271 -55
- package/dist/schemas.cjs +58 -81
- package/dist/schemas.cjs.map +1 -1
- package/dist/schemas.d.cts +88 -225
- package/dist/schemas.d.ts +88 -225
- package/dist/schemas.js +58 -76
- package/dist/schemas.js.map +1 -1
- package/dist/{tasks-BH_i_pVb.d.cts → tasks-ByJCQ5BI.d.cts} +3003 -2508
- package/dist/{tasks-BH_i_pVb.d.ts → tasks-ByJCQ5BI.d.ts} +3003 -2508
- package/dist/{types-0c6_NGU4.d.cts → types-DlfADjPR.d.cts} +17 -2
- package/dist/{types-0c6_NGU4.d.ts → types-DlfADjPR.d.ts} +17 -2
- package/package.json +9 -7
- package/dist/chunk-HRUMZ46V.js.map +0 -1
- package/dist/chunk-P2E6LU7I.js.map +0 -1
- package/dist/chunk-R3C63RZJ.js.map +0 -1
package/dist/realtime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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":"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","names":["defaultClient"]}
|
|
1
|
+
{"version":3,"sources":["../src/realtime/types.ts","../src/realtime/login-token.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 /**\n * The credential for one `login` frame, fetched once per connect attempt —\n * reconnects included — and never reused.\n *\n * In a browser this mints a single-use `gr_wst_` token same-origin (the\n * server refuses anything else from a browser handshake), so page script\n * never holds the account session. A non-browser client (Node, a CLI) may\n * return its bearer credential. Resolve `null` when no token can be had;\n * the client treats that attempt as failed and retries on its backoff.\n *\n * The provider's identity is part of the session: replacing it with a\n * different function reconnects the socket.\n */\n getLoginToken: RealtimeLoginTokenProvider;\n}\n\n/** Mints the token for one realtime `login` frame. See {@link RealtimeSessionBase}. */\nexport type RealtimeLoginTokenProvider = () => Promise<string | null>;\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","import type { RealtimeLoginTokenProvider } from './types';\n\n/**\n * How long one connect attempt waits for its login token. A provider that\n * never settles (a hung mint request) must not hold the attempt open forever:\n * past this the attempt fails like any other and the caller's backoff applies.\n */\nexport const LOGIN_TOKEN_TIMEOUT_MS = 10_000;\n\n/**\n * Call `provider` once, bounded by {@link LOGIN_TOKEN_TIMEOUT_MS}.\n *\n * Resolves the token, or `null` when the provider throws, times out, or\n * returns anything but a non-empty string. Never rejects.\n */\nexport async function mintLoginToken(provider: RealtimeLoginTokenProvider): Promise<string | null> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n const token: unknown = await Promise.race([\n provider(),\n new Promise<null>((resolve) => {\n timer = setTimeout(() => resolve(null), LOGIN_TOKEN_TIMEOUT_MS);\n }),\n ]);\n return typeof token === 'string' && token.length > 0 ? token : null;\n } catch {\n return null;\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n }\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 { mintLoginToken } from './login-token';\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 * Bumped by every connect attempt and every deliberate close. A connect\n * attempt awaits its login token before it opens a socket; if the\n * generation moved while it waited, a close or a newer attempt owns the\n * state and the stale attempt stops without touching it.\n */\n connectGeneration: number;\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 connectGeneration: 0,\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 * A different login-token provider always does — it stands for a different\n * identity; the rest is mount-specific — a workspace client changing\n * workspace is a different socket, and an account client has no such axis.\n */\n protected sessionRequiresReconnect(previous: TSession | null, next: TSession): boolean {\n return previous?.getLoginToken !== next.getLoginToken || 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 // `connecting` with no `ws` is an attempt still minting its token under\n // the old session; closing bumps the generation so it stops there.\n if (changed && (this.state.ws || this.state.connecting)) {\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 const generation = ++this.state.connectGeneration;\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 void this.openSocket(session, generation);\n }\n\n /**\n * Mint this attempt's login token, then open the socket with it.\n *\n * A fresh token per attempt, reconnects included: a browser's token is\n * single-use and expires in seconds, so a token from an earlier attempt is\n * already spent.\n */\n private async openSocket(session: TSession, generation: number): Promise<void> {\n const token = await mintLoginToken(session.getLoginToken);\n if (generation !== this.state.connectGeneration) return;\n if (!token) {\n this.releaseUnopenedAttempt();\n return;\n }\n\n let ws: WebSocket;\n try {\n ws = new WebSocket(this.buildUrl(session));\n } catch {\n // A bad URL throws synchronously; without this the attempt would stay\n // `connecting` forever and every later `ensureConnection` would no-op.\n this.releaseUnopenedAttempt();\n return;\n }\n\n ws.onopen = () => {\n ws.send(JSON.stringify({ type: 'login', token }));\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 this.afterConnectionEnded();\n };\n\n ws.onerror = () => {\n // onclose will fire after this\n };\n\n this.state.ws = ws;\n }\n\n /**\n * End an attempt that never opened a socket. No `onclose` will run for it,\n * so the retry decision is made here, on the normal backoff.\n */\n private releaseUnopenedAttempt(): void {\n this.state.connecting = false;\n this.afterConnectionEnded();\n }\n\n /** Decide whether a connection that just ended (or never opened) retries. */\n private afterConnectionEnded(): void {\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 protected closeConnection(): void {\n this.state.intentionalClose = true;\n this.state.connectGeneration += 1;\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`: log in with a token from the session's\n * `getLoginToken` provider (fresh per connect attempt),\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 login 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, getLoginToken });\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 { mintLoginToken } from './login-token';\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 /** Bumped by every connect and disconnect; a stale connect stops at its next await. */\n private connectGeneration = 0;\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 const generation = ++this.connectGeneration;\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 // One token per connect: a browser's is single-use and expires in seconds.\n const token = await mintLoginToken(session.getLoginToken);\n if (generation !== this.connectGeneration) {\n throw new Error('Operator session connect superseded');\n }\n if (!token) {\n this.setSessionState('error');\n this.setCallPhase('idle');\n throw new Error('Realtime login token unavailable');\n }\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 }));\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.connectGeneration += 1;\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":["defaultClient"]}
|