@ai-matrx/meet 0.11.28 → 0.11.30

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/dist/core.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as ParticipantIdentity, k as AdmissionState, a1 as NoteTakerState, a as MeetParticipant, M as MeetingRecord, R as RoomName, d as CallInvite, e as ParticipantRole, a6 as ParticipantStanding, a3 as ObserversVisibleTo, S as StageLayout, L as LobbyEntry, aa as RecordingState, U as UserId, aE as RecordingSource, ae as TranscriptSegment, g as MeetingNote, W as WrapUpAnnouncement, j as CollabSnapshot, p as ConnectionQuality, h as MeetingId, b as MeetApi, f as CallMode, O as OrganizationId, m as CallId, n as CallState, Z as MeetingKind, a8 as RecordingPolicy, V as MeetingAttendee, a9 as RecordingRecord, a0 as MeetingSettings, X as MeetingChanges, a4 as OccurrenceOverride, $ as MeetingOccurrence, af as UpcomingOccurrence, Y as MeetingInvitee, I as InviteeInput, q as InviteeRole, ab as RsvpAnswer, ac as RsvpInvitation, c as MeetIdentity, C as CollabPanel } from './collab-CUxjUtrb.cjs';
2
- export { A as AccessTokenSource, l as CAPTION_LANGUAGES, o as CallablePerson, E as EMPTY_COLLAB, F as FILE_SESSION_PATH, J as JsonObject, r as JsonValue, s as MeetApiOptions, t as MeetApiRequest, u as MeetAppFocus, v as MeetBreakoutBroadcast, w as MeetBreakoutPlan, x as MeetBreakoutRoom, y as MeetCollabSettings, z as MeetFileMedia, B as MeetPoll, D as MeetPollOption, G as MeetPollState, H as MeetQuestion, K as MeetQuestionState, N as MeetStroke, Q as MeetingActivities, i as MeetingAppPanel, _ as MeetingNoteKind, a2 as ORGANIZATION_HEADER, a5 as OccurrenceState, a7 as ROLE_RANK, ad as RsvpState, T as TranscriptSource, ag as appPanelOf, ah as applyCollabPacket, ai as asCallId, aj as asMeetingId, ak as asOrganizationId, al as asParticipantIdentity, am as asRoomName, an as asUserId, ao as assignedRoom, ap as autoAssign, aq as breakoutRemainingMs, ar as captureHeldOf, as as collabFromState, at as createMeetApi, au as defaultCaptionLanguage, av as fileDownloadPath, aw as focusFromRow, ax as isGuest, ay as isHostRole, az as isLinkFollower, aA as isObserverRole, aB as loadMeetingActivities, aC as scheduleSettingsPatch, aD as withItem } from './collab-CUxjUtrb.cjs';
1
+ import { P as ParticipantIdentity, k as AdmissionState, a1 as NoteTakerState, a as MeetParticipant, M as MeetingRecord, R as RoomName, d as CallInvite, e as ParticipantRole, a6 as ParticipantStanding, a3 as ObserversVisibleTo, S as StageLayout, L as LobbyEntry, aa as RecordingState, U as UserId, aE as RecordingSource, ae as TranscriptSegment, g as MeetingNote, W as WrapUpAnnouncement, j as CollabSnapshot, p as ConnectionQuality, h as MeetingId, b as MeetApi, f as CallMode, O as OrganizationId, m as CallId, n as CallState, Z as MeetingKind, a8 as RecordingPolicy, V as MeetingAttendee, a9 as RecordingRecord, a0 as MeetingSettings, X as MeetingChanges, a4 as OccurrenceOverride, $ as MeetingOccurrence, af as UpcomingOccurrence, Y as MeetingInvitee, I as InviteeInput, q as InviteeRole, ab as RsvpAnswer, ac as RsvpInvitation, c as MeetIdentity, C as CollabPanel } from './collab-XY6Xj1dG.cjs';
2
+ export { A as AccessTokenSource, l as CAPTION_LANGUAGES, o as CallablePerson, E as EMPTY_COLLAB, F as FILE_SESSION_PATH, J as JsonObject, r as JsonValue, s as MeetApiOptions, t as MeetApiRequest, u as MeetAppFocus, v as MeetBreakoutBroadcast, w as MeetBreakoutPlan, x as MeetBreakoutRoom, y as MeetCollabSettings, z as MeetFileMedia, B as MeetPoll, D as MeetPollOption, G as MeetPollState, H as MeetQuestion, K as MeetQuestionState, N as MeetStroke, Q as MeetingActivities, i as MeetingAppPanel, _ as MeetingNoteKind, a2 as ORGANIZATION_HEADER, a5 as OccurrenceState, a7 as ROLE_RANK, ad as RsvpState, T as TranscriptSource, ag as appPanelOf, ah as applyCollabPacket, ai as asCallId, aj as asMeetingId, ak as asOrganizationId, al as asParticipantIdentity, am as asRoomName, an as asUserId, ao as assignedRoom, ap as autoAssign, aq as breakoutRemainingMs, ar as captureHeldOf, as as collabFromState, at as createMeetApi, au as defaultCaptionLanguage, av as fileDownloadPath, aw as focusFromRow, ax as isGuest, ay as isHostRole, az as isLinkFollower, aA as isObserverRole, aB as loadMeetingActivities, aC as scheduleSettingsPatch, aD as withItem } from './collab-XY6Xj1dG.cjs';
3
3
  import { RealtimeManager } from '@ai-matrx/realtime';
4
4
  import { MessagingSupabaseClient, ActionHandler } from '@ai-matrx/messaging';
5
5
  import { RoomOptions, ConnectionQuality as ConnectionQuality$1, Participant } from 'livekit-client';
@@ -152,12 +152,55 @@ declare function displayNameFor(book: NameBook, identity: ParticipantIdentity |
152
152
  * there is no second wording to keep in sync.
153
153
  */
154
154
 
155
+ /**
156
+ * WHY a device is unusable, in the contract's words (CORE-DESIGN §3.2, the `mic` / `camera`
157
+ * region's `unavailable:<reason>`). The media supervisor (`media-supervisor.ts`) decides it from
158
+ * the browser's error AND the Permissions API, because the error alone cannot tell a person's
159
+ * Block from an operating-system refusal or a stale device id.
160
+ */
161
+ type DeviceUnavailableReason = "denied" | "blocked_site" | "blocked_os" | "none" | "busy" | "failed" | "revoked";
155
162
  /** Why a local track is not publishing, in the browser's terms plus a remedy. */
156
163
  interface MediaUnavailable {
157
164
  readonly code: string;
158
165
  readonly message: string;
159
166
  readonly remedy: string;
167
+ /** The region reason (S6c). Absent on states recorded before the supervisor classified them. */
168
+ readonly reason?: DeviceUnavailableReason | undefined;
169
+ }
170
+ /** What the browser's permission store says for one device kind. `unknown` = no Permissions API. */
171
+ type PermissionReading = "granted" | "denied" | "prompt" | "unknown";
172
+ /** A device change the person is told about (S6c): unplugged → switched, or nothing left. */
173
+ interface DeviceNotice {
174
+ readonly id: number;
175
+ readonly kind: "microphone" | "camera";
176
+ /** `switched` = the meeting moved to another device; `lost` = none of this kind is left. */
177
+ readonly outcome: "switched" | "lost";
178
+ /** The device that went away, by label (empty when the browser hid labels). */
179
+ readonly from: string;
180
+ /** The device now in use (`switched` only). */
181
+ readonly to: string | null;
182
+ }
183
+ /** A device that just appeared — offered, never switched to silently (catalog `device-default-changed`). */
184
+ interface DeviceOffer {
185
+ readonly kind: "microphone" | "camera";
186
+ readonly deviceId: string;
187
+ readonly label: string;
160
188
  }
189
+ /**
190
+ * THE MEDIA REGIONS THE SUPERVISOR OWNS (CORE-DESIGN §3.2 `audio_output`, and the inputs the
191
+ * `mic` / `camera` regions are derived from). None of it can change the call phase.
192
+ */
193
+ interface MediaRegionState {
194
+ readonly permission: {
195
+ readonly microphone: PermissionReading;
196
+ readonly camera: PermissionReading;
197
+ };
198
+ /** `blocked` = the browser's autoplay policy is holding remote audio until a gesture. */
199
+ readonly audioOutput: "ok" | "blocked";
200
+ readonly notice: DeviceNotice | null;
201
+ readonly offer: DeviceOffer | null;
202
+ }
203
+ declare const IDLE_MEDIA_REGION: MediaRegionState;
161
204
  interface LocalTrackState {
162
205
  /** True only when the track is actually published. Never optimistic. */
163
206
  readonly enabled: boolean;
@@ -174,7 +217,7 @@ interface LocalMediaState {
174
217
  }
175
218
  declare const IDLE_TRACK: LocalTrackState;
176
219
  declare const IDLE_MEDIA: LocalMediaState;
177
- declare function unavailableFrom(error: MeetError): MediaUnavailable;
220
+ declare function unavailableFrom(error: MeetError, reason?: DeviceUnavailableReason): MediaUnavailable;
178
221
  /**
179
222
  * THE SENTENCE. One definition, so the room chrome, a host's own chrome and the
180
223
  * tests cannot say three different things about the same fact.
@@ -262,6 +305,20 @@ type PhaseEvent =
262
305
  readonly type: "join";
263
306
  readonly click: true;
264
307
  }
308
+ /**
309
+ * S6b: the connection supervisor rejoins by itself from `disconnected` once the network and our
310
+ * servers answer (`auto_rejoin`). Not a click — and it can only ever lead to `joining`.
311
+ */
312
+ | {
313
+ readonly type: "auto_rejoin";
314
+ }
315
+ /**
316
+ * S6b: the page reloaded while this tab was in (or dropped from) the call — Chrome's offline
317
+ * page reloading, a refresh — so `resolving` goes straight back to `joining`, never pre-join.
318
+ */
319
+ | {
320
+ readonly type: "resumed";
321
+ }
265
322
  /** The person chose to go back to the device check from `left` / `disconnected`. */
266
323
  | {
267
324
  readonly type: "back_to_prejoin";
@@ -571,6 +628,11 @@ interface MeetSnapshot {
571
628
  readonly roomName: RoomName | null;
572
629
  /** The `connection` region: reconnecting is here, never a phase. */
573
630
  readonly connection: ConnectionRegion;
631
+ /**
632
+ * Whether the reconnect banner shows: the region is honest at once, the banner only after the
633
+ * drop outlasts `reconnect_banner_delay_ms` (the connection supervisor sets it, §3.3 CS).
634
+ */
635
+ readonly connectionBanner: boolean;
574
636
  /** The `share` region. */
575
637
  readonly share: ShareRegion;
576
638
  /**
@@ -683,6 +745,11 @@ interface MeetSnapshot {
683
745
  * microphone is a state with a remedy, not a failed join.
684
746
  */
685
747
  readonly media: LocalMediaState;
748
+ /**
749
+ * S6c — the media supervisor's regions: permission per kind, autoplay (`audio_output`), the
750
+ * last device change and an offered new device. Written only by `media-supervisor.ts`.
751
+ */
752
+ readonly mediaRegion: MediaRegionState;
686
753
  /** The last failure, with its remedy. Never null-and-hoping. */
687
754
  readonly failure: {
688
755
  readonly message: string;
@@ -1799,7 +1866,9 @@ interface DriverParticipant {
1799
1866
  readonly cameraEnabled: boolean;
1800
1867
  readonly screenSharing: boolean;
1801
1868
  }
1802
- type DriverConnectionState = "connecting" | "connected" | "reconnecting" | "disconnected";
1869
+ type DriverConnectionState = "connecting" | "connected"
1870
+ /** Only the signal connection is being re-established; media may still flow. */
1871
+ | "signal_reconnecting" | "reconnecting" | "disconnected";
1803
1872
  type DriverTrackKind = "camera" | "microphone" | "screen";
1804
1873
  /**
1805
1874
  * ONE LIVE CAPTION off the `lk.transcription` text stream.
@@ -1837,6 +1906,12 @@ interface RoomDriverHandlers {
1837
1906
  data(topic: string, payload: string, fromIdentity: string | null): void;
1838
1907
  /** A live caption. Interim and final both arrive here (D16). */
1839
1908
  transcription(event: DriverTranscription): void;
1909
+ /**
1910
+ * A remote participant left the room, with LiveKit's reason by NAME (`CLIENT_INITIATED`, …) or
1911
+ * null when none was given. Fired BEFORE the roster update that drops them, so an unannounced
1912
+ * departure can be held as a `reconnecting` tile (S6b). Optional for older host drivers.
1913
+ */
1914
+ participantLeft?(identity: string, reason: string | null): void;
1840
1915
  /** The room ended for this client. `reason` is shown to the user verbatim. */
1841
1916
  disconnected(reason: string | null): void;
1842
1917
  /**
@@ -1846,6 +1921,15 @@ interface RoomDriverHandlers {
1846
1921
  * connect and on every change. Optional so an older host driver still works.
1847
1922
  */
1848
1923
  roomMetadata?(metadata: string): void;
1924
+ /**
1925
+ * S6c — MEDIA. Can remote audio play (`false` = the browser's autoplay policy is holding it
1926
+ * until a gesture)? Fired on every change and once right after connect. Optional for older drivers.
1927
+ */
1928
+ audioPlayback?(canPlay: boolean): void;
1929
+ /** S6c — the device list changed (plugged / unplugged), AFTER the SDK re-picked defaults. */
1930
+ devicesChanged?(): void;
1931
+ /** S6c — the SDK could not acquire or keep a local device. `kind` when the SDK knows it. */
1932
+ mediaDeviceError?(error: unknown, kind: "audioinput" | "videoinput" | null): void;
1849
1933
  }
1850
1934
  /** A camera background effect. `image` replaces the background with `imageUrl`. */
1851
1935
  type VideoEffect = {
@@ -1898,6 +1982,11 @@ interface MeetRoomDriver {
1898
1982
  /** The device the driver actually selected, so the UI never lies about it. */
1899
1983
  activeDevices(): Readonly<Record<"audioinput" | "videoinput" | "audiooutput", string | null>>;
1900
1984
  switchDevice(kind: "audioinput" | "videoinput" | "audiooutput", deviceId: string): Promise<void>;
1985
+ /**
1986
+ * S6c — resume remote audio the autoplay policy blocked. MUST run inside the person's click
1987
+ * (the gesture is what the browser checks). Optional: a driver without it never blocks.
1988
+ */
1989
+ startAudio?(): Promise<void>;
1901
1990
  }
1902
1991
  type RoomDriverFactory = (handlers: RoomDriverHandlers) => MeetRoomDriver | Promise<MeetRoomDriver>;
1903
1992
  /** Topics for the in-room control plane. One place, so senders cannot drift. */
@@ -2154,6 +2243,448 @@ declare function createRecordingController(options: RecordingControllerOptions):
2154
2243
  /** The indicator's words. One definition, so no surface invents a softer one. */
2155
2244
  declare function recordingLabel(state: RecordingState): string | null;
2156
2245
 
2246
+ /**
2247
+ * MS — THE MEDIA SUPERVISOR (CORE-DESIGN §3.3, step S6c).
2248
+ *
2249
+ * Owns the media regions of §3.2 that are about THIS person's devices and speakers:
2250
+ *
2251
+ * - **Permission per kind.** The browser's error alone cannot tell a person's Block from an
2252
+ * operating-system refusal or a stale device id (`NotAllowedError` is all three in Chrome), so
2253
+ * every refusal is classified against the Permissions API: `denied` there is the person's (or
2254
+ * the site's) Block; a refusal while the browser says `granted` is the operating system. A kind
2255
+ * whose permission is `denied` reads `unavailable:denied` even when nothing tried to open it —
2256
+ * a camera the browser will not hand over is not "off by choice".
2257
+ * - **Live device switching.** LiveKit falls back to the default for a lost MICROPHONE by itself
2258
+ * but MUTES a lost camera (client-sdk-js #2035). On every device change the supervisor checks
2259
+ * the devices in use against the devices present: a vanished camera moves to the next camera;
2260
+ * a vanished microphone is reported with the device LiveKit moved to; nothing left of a kind is
2261
+ * `unavailable:none`. Each is a notice — never a silent switch. A NEW device is offered, never
2262
+ * taken (a person mid-sentence does not want their mic to jump to a headset being charged).
2263
+ * - **Autoplay.** `audio_output` = `blocked` while the browser holds remote audio until a gesture;
2264
+ * `enableAudio()` runs inside the person's click; the check is repeated when the tab comes back.
2265
+ *
2266
+ * 🚨 NOTHING HERE CHANGES THE CALL PHASE (§3.1 invariant). Every outcome is region state.
2267
+ * 🚨 NO BROWSER GLOBAL IS TOUCHED HERE: the browser half is the `MediaPlatform` port (§3.5) —
2268
+ * `webMediaPlatform()` below is the web adapter; a React Native host passes its own.
2269
+ */
2270
+
2271
+ type MediaKind = "microphone" | "camera";
2272
+ type BrowserFamily = "chrome" | "edge" | "firefox" | "safari" | "other";
2273
+ type OsFamily = "mac" | "windows" | "ios" | "android" | "linux" | "other";
2274
+ interface PlatformDevice {
2275
+ readonly deviceId: string;
2276
+ readonly kind: string;
2277
+ readonly label: string;
2278
+ }
2279
+ /** The slice of §3.5's `MeetPlatform` the media supervisor needs. */
2280
+ interface MediaPlatform {
2281
+ readonly browser: BrowserFamily;
2282
+ readonly os: OsFamily;
2283
+ /** The Permissions API reading, or `unknown` where the browser has none for this kind. */
2284
+ queryPermission(kind: MediaKind): Promise<PermissionReading>;
2285
+ /** Call `onChange` whenever the permission for `kind` changes. Returns the unsubscribe. */
2286
+ watchPermission(kind: MediaKind, onChange: (reading: PermissionReading) => void): () => void;
2287
+ listDevices(): Promise<readonly PlatformDevice[]>;
2288
+ /** The tab became visible again (autoplay is re-checked then). Returns the unsubscribe. */
2289
+ onVisible(callback: () => void): () => void;
2290
+ }
2291
+ /**
2292
+ * The region reason for a refused device, from the error AND the permission store.
2293
+ * Pure — the rules are the tests.
2294
+ */
2295
+ declare function classifyDeviceFailure(error: {
2296
+ readonly code: string;
2297
+ readonly message: string;
2298
+ }, permission: PermissionReading): DeviceUnavailableReason;
2299
+ /** The contract notice code for an unusable device (HARNESS-CONTRACT §4). */
2300
+ declare function deviceNoticeCode(kind: MediaKind, reason: DeviceUnavailableReason): "mic-blocked" | "camera-blocked" | "device-lost";
2301
+ /**
2302
+ * The region reason this track is in, or null when the device is usable (on, or off by choice).
2303
+ * A kind the browser's permission store denies is `denied` even when nothing tried to open it.
2304
+ */
2305
+ declare function deviceUnavailableReason(track: LocalTrackState, permission: PermissionReading): DeviceUnavailableReason | null;
2306
+ /** The short chip text — the skin's words may differ; the reason is the fact. */
2307
+ declare function deviceChipLabel(kind: MediaKind, reason: DeviceUnavailableReason): string;
2308
+ /**
2309
+ * THE REAL REMEDY, per browser and operating system: the steps a person actually follows. Short
2310
+ * lines (interface text is layout); the last step is always the Try again the screen offers.
2311
+ */
2312
+ declare function deviceFixSteps(kind: MediaKind, reason: DeviceUnavailableReason, where: {
2313
+ readonly browser: BrowserFamily;
2314
+ readonly os: OsFamily;
2315
+ }): readonly string[];
2316
+ declare function detectBrowser(userAgent: string): BrowserFamily;
2317
+ declare function detectOs(userAgent: string, maxTouchPoints?: number): OsFamily;
2318
+ /** The browser adapter, or null outside a browser (SSR, a test, React Native without one). */
2319
+ declare function webMediaPlatform(): MediaPlatform | null;
2320
+ interface MediaSupervisorOptions {
2321
+ readonly store: MeetStore;
2322
+ readonly platform: MediaPlatform | null;
2323
+ /** The live room's driver, or null between rooms. */
2324
+ readonly driver: () => MeetRoomDriver | null;
2325
+ readonly report: (level: "info" | "warn" | "error", message: string, remedy?: string) => void;
2326
+ }
2327
+ interface MediaSupervisor {
2328
+ /** Begin watching permissions and visibility (idempotent). */
2329
+ start(): void;
2330
+ stop(): void;
2331
+ /** The `MediaUnavailable` for a refused device: the browser's words + the region reason. */
2332
+ unavailable(error: unknown, kind: MediaKind, operation: string): Promise<MediaUnavailable>;
2333
+ /** Driver handlers (wired by the engine). */
2334
+ onAudioPlayback(canPlay: boolean): void;
2335
+ onDevicesChanged(): void;
2336
+ onDeviceError(error: unknown, kind: "audioinput" | "videoinput" | null): void;
2337
+ /** The engine connected a room: remember which devices are in use now. */
2338
+ onConnected(): void;
2339
+ /** The room is gone: autoplay and device notices belong to the room. */
2340
+ onDisconnected(): void;
2341
+ /** "Enable sound" — call from inside the person's click. */
2342
+ enableAudio(): Promise<void>;
2343
+ /** Use the offered new device. */
2344
+ acceptOffer(): Promise<void>;
2345
+ dismissOffer(): void;
2346
+ dismissNotice(): void;
2347
+ }
2348
+ declare function createMediaSupervisor(options: MediaSupervisorOptions): MediaSupervisor;
2349
+
2350
+ /**
2351
+ * THE ROOM ENGINE — the object a host mounts once and never reasons about.
2352
+ *
2353
+ * It owns every sequence a calling product gets wrong: ask for a token, connect,
2354
+ * project the roster, carry the in-room control plane, keep the phase honest
2355
+ * through a reconnect, and tear everything down exactly once. A consumer's job
2356
+ * is `engine.join(...)` and rendering a snapshot.
2357
+ *
2358
+ * THREE rules are structural here, not documented-and-hoped:
2359
+ *
2360
+ * 1. **The phase never lies.** `connecting` becomes `connected` only when the
2361
+ * driver says so, and any failure becomes `failed` WITH the remedy attached
2362
+ * to the snapshot. There is no path that leaves a spinner running forever —
2363
+ * the single most common complaint about every video product.
2364
+ * 2. **Role comes from the TOKEN, never from the client.** A participant's role
2365
+ * is read out of the server-minted metadata. A client that could decide it
2366
+ * was host could mute anyone; here the claim is unforgeable because the
2367
+ * server signed it.
2368
+ * 3. **Teardown is idempotent and total.** Leaving twice, or unmounting during
2369
+ * a connect, disconnects once and leaves no listener behind. The half-torn
2370
+ * room is what produces "my camera light stays on after the call".
2371
+ */
2372
+
2373
+ interface JoinRequest {
2374
+ readonly roomName: RoomName;
2375
+ readonly meetingId?: string | undefined;
2376
+ readonly slug?: string | undefined;
2377
+ /** `audio` joins with the camera off. Same room, same code path (D3). */
2378
+ readonly mode: CallMode;
2379
+ /**
2380
+ * Whether to ATTEMPT the microphone on join. Defaults to true. `false` is a
2381
+ * deliberate listen-only join — the pre-join screen's "Join with microphone"
2382
+ * toggle. Either way the join itself never depends on the outcome; a refused
2383
+ * device lands in `snapshot.media.microphone.unavailable`, not in a throw.
2384
+ */
2385
+ readonly microphone?: boolean | undefined;
2386
+ /** Meet wave 5: a breakout room of this meeting; absent/null = the main room. */
2387
+ readonly breakoutRoomId?: string | null | undefined;
2388
+ /** HR-360 wave 2 (MD-16): ask to join as a silent observer. The server decides. */
2389
+ readonly observe?: boolean | undefined;
2390
+ }
2391
+ interface RoomEngineOptions {
2392
+ store: MeetStore;
2393
+ tokens: TokenClient;
2394
+ identity: MeetIdentity;
2395
+ driverFactory: RoomDriverFactory;
2396
+ /** Canonical public metadata read; room packets alone never authorize teardown. */
2397
+ resolveMeeting?: ((slug: string) => Promise<MeetingRecord>) | undefined;
2398
+ onDiagnostic?: ((event: {
2399
+ level: "info" | "warn" | "error";
2400
+ message: string;
2401
+ remedy?: string;
2402
+ }) => void) | undefined;
2403
+ /** Fired when the local participant leaves for any reason, including kicked. */
2404
+ onLeft?: ((reason: string | null) => void) | undefined;
2405
+ now?: (() => number) | undefined;
2406
+ /** Remembered noise suppression / background choice (Meet wave 2). */
2407
+ effectsStorage?: EffectsStorage | undefined;
2408
+ /**
2409
+ * The device the person PICKED for each kind (the pre-join pickers, the
2410
+ * settings panel). Applied before any track is created — through 0.7.x the
2411
+ * pickers stored a choice nothing ever read, so every join used the browser
2412
+ * default whatever the screen said.
2413
+ */
2414
+ resolveDevice?: ((kind: "audioinput" | "videoinput" | "audiooutput") => string | null) | undefined;
2415
+ /**
2416
+ * §3.4 EXIT-REASON RESOLUTION: on every LiveKit disconnect the engine asks the server's state
2417
+ * read what happened to THIS connection (`GET /v1/meet/state?connection=`). Resolves null when
2418
+ * the read failed (offline / unreachable), undefined when this room has no state read (a call).
2419
+ */
2420
+ readExit?: ((connection: string) => Promise<ExitServerWord | null | undefined>) | undefined;
2421
+ /** Was the page frozen when the connection dropped (Page Lifecycle)? → `inactive`. */
2422
+ wasFrozen?: (() => boolean) | undefined;
2423
+ /**
2424
+ * S6b — the connection supervisor's ports. `network` = does the device have a network
2425
+ * (default: `navigator.onLine`); `probe` = do our API and the media server answer (default:
2426
+ * assume yes); `resume` = per-tab storage for the reload-after-offline marker (default: none).
2427
+ */
2428
+ /**
2429
+ * S6c — the media supervisor's platform port (permissions, device list, visibility). Default:
2430
+ * the browser's (`webMediaPlatform()`); `null` turns the supervisor's browser reads off.
2431
+ */
2432
+ mediaPlatform?: MediaPlatform | null | undefined;
2433
+ connection?: {
2434
+ readonly network?: NetworkPort | undefined;
2435
+ readonly probe?: ((serverUrl: string | null) => Promise<boolean>) | undefined;
2436
+ readonly resume?: ResumeStorage | undefined;
2437
+ } | undefined;
2438
+ }
2439
+ interface RoomEngine {
2440
+ join(request: JoinRequest): Promise<void>;
2441
+ /**
2442
+ * 🚨 THE HOST SAID YES — COME IN.
2443
+ *
2444
+ * A lobby token is minted with `canPublish: false` and `canSubscribe: false`
2445
+ * (that is what makes the lobby real rather than a screen over a live
2446
+ * microphone), so admission is not a UI state change: the cached token has to
2447
+ * be thrown away and a NEW one minted with the grants the server now gives.
2448
+ * Without this the waiting room's own sentence — "you will join automatically
2449
+ * once you are admitted" — is a lie, and the guest waits forever.
2450
+ */
2451
+ rejoin(): Promise<void>;
2452
+ /**
2453
+ * S6b: rejoin BY ITSELF from `disconnected` (the connection supervisor calls this once the
2454
+ * network and our servers answer). A fresh mint into the same identity: the server restores
2455
+ * role, hand and admission from the person row.
2456
+ */
2457
+ autoRejoin(): Promise<void>;
2458
+ /**
2459
+ * S6b: this tab was in (or dropped from) the call before the page reloaded — Chrome's offline
2460
+ * page reloading, a refresh. Returns true when it went straight back to joining (from
2461
+ * `resolving`), false when there is nothing fresh to resume (the caller resolves as usual).
2462
+ */
2463
+ resume(roomName: RoomName): boolean;
2464
+ /**
2465
+ * Meet wave 5: go to a breakout room (`null` = back to the main room). The server
2466
+ * mints the pass (`/token` with `breakout_room_id`) and refuses a room the plan does
2467
+ * not allow; the move is a reconnect that never reads as leaving the meeting.
2468
+ */
2469
+ moveToBreakout(roomId: string | null): Promise<void>;
2470
+ leave(): Promise<void>;
2471
+ /** The person's own "back to the device check" press from `left` / `disconnected`. */
2472
+ backToPrejoin(): void;
2473
+ /** Clear a refused-share notice (the `share` region) the person dismissed. */
2474
+ dismissShareNotice(): void;
2475
+ setMicrophoneEnabled(enabled: boolean): Promise<void>;
2476
+ setCameraEnabled(enabled: boolean): Promise<void>;
2477
+ setScreenShareEnabled(enabled: boolean): Promise<void>;
2478
+ raiseHand(raised: boolean): Promise<void>;
2479
+ react(emoji: string): Promise<void>;
2480
+ /**
2481
+ * A PRIVATE message to one person (Meet wave 2). Data plane only, addressed
2482
+ * to that identity, NEVER saved — see `core/chat.ts` for the saved,
2483
+ * everyone-chat, which goes through the server.
2484
+ */
2485
+ sendPrivateChat(to: ParticipantIdentity, text: string): Promise<void>;
2486
+ /**
2487
+ * Change this participant's own media processing — noise suppression and the
2488
+ * camera background — on the live tracks, and remember it in this browser.
2489
+ * A refused effect is a diagnostic and a revert, never a lost camera.
2490
+ */
2491
+ setEffects(next: Partial<MediaEffects>): Promise<void>;
2492
+ /** Whether this browser can run a background effect at all (absent, never dead). */
2493
+ supportsVideoEffects(): boolean;
2494
+ /** Whether this browser can share a screen at all — the control is ABSENT where it cannot. */
2495
+ supportsScreenShare(): boolean;
2496
+ /** S6c: "Enable sound" — resume remote audio the autoplay policy holds. Call inside the click. */
2497
+ enableAudio(): Promise<void>;
2498
+ /** S6c: switch to the device the supervisor offered (a new one was plugged in). */
2499
+ acceptDeviceOffer(): Promise<void>;
2500
+ dismissDeviceOffer(): void;
2501
+ dismissDeviceNotice(): void;
2502
+ /** Choose the speaker (audio output) for this meeting. */
2503
+ switchDevice(kind: "audioinput" | "videoinput" | "audiooutput", deviceId: string): Promise<void>;
2504
+ pin(identity: ParticipantIdentity | null): void;
2505
+ setLayout(layout: StageLayout): void;
2506
+ attach(identity: ParticipantIdentity, kind: "camera" | "microphone" | "screen", element: HTMLMediaElement): () => void;
2507
+ token(): RoomToken | null;
2508
+ dispose(): Promise<void>;
2509
+ }
2510
+ /** Where this browser keeps a participant's media choices. */
2511
+ interface EffectsStorage {
2512
+ load(): MediaEffects;
2513
+ save(effects: MediaEffects): void;
2514
+ }
2515
+ /**
2516
+ * 🚨 THE SERVER'S WORD, ON TWO CARRIERS (MRI-A11, feedback `7418ef78`).
2517
+ *
2518
+ * aidream mints the role into the token's participant METADATA (a JSON blob)
2519
+ * AND as a LiveKit participant ATTRIBUTE. Both come out of one decision in
2520
+ * `services/meet/service.py`; neither can be written by a client. The attribute
2521
+ * is preferred because LiveKit delivers the attribute map on every roster
2522
+ * update, while a roster event can carry an empty metadata string — which is
2523
+ * precisely how, through 2026-09-08, EVERY participant projected as
2524
+ * "participant" and no host ever saw a host control in a browser.
2525
+ *
2526
+ * These strings are pinned on the server side too: `test_meet_service.py` reads
2527
+ * them out of THIS file and fails if the two sides drift.
2528
+ */
2529
+ declare const PARTICIPANT_ROLE_ATTRIBUTE = "matrx.participant.role";
2530
+ declare const PARTICIPANT_CAN_RECORD_ATTRIBUTE = "matrx.participant.can_record";
2531
+ /** Standing travels beside the role (CORE-DESIGN §2.1). Pinned by aidream's tests too. */
2532
+ declare const PARTICIPANT_STANDING_ATTRIBUTE = "matrx.participant.standing";
2533
+ /**
2534
+ * Metadata is SERVER-MINTED and therefore trusted; it is still parsed
2535
+ * defensively, because a malformed blob must degrade a tile to "participant"
2536
+ * rather than throw inside a roster event and freeze the whole grid.
2537
+ */
2538
+ declare function projectParticipant(driver: DriverParticipant, joinedAt: string): MeetParticipant;
2539
+ declare function createRoomEngine(options: RoomEngineOptions): RoomEngine;
2540
+ /** Exported for the host-power check in UI code — one definition, one rank. */
2541
+ declare function canModerate(participant: MeetParticipant | undefined): boolean;
2542
+ declare function hostUserIds(participants: readonly MeetParticipant[]): readonly UserId[];
2543
+
2544
+ /**
2545
+ * CS — THE CONNECTION SUPERVISOR (CORE-DESIGN §3.3, step S6b).
2546
+ *
2547
+ * The one owner of "is this tab still connected, and if not, why, and what happens next". It
2548
+ * never touches a browser global itself — the network signal and the reachability probe arrive
2549
+ * as ports (`NetworkPort`, `probe`), with web implementations below for the provider to hand in —
2550
+ * and it never moves the phase except through `store.dispatch` (via the engine's `autoRejoin`).
2551
+ *
2552
+ * What it decides:
2553
+ *
2554
+ * 1. **The `connection` region** (§3.2): `stable · signal_reconnecting · reconnecting · offline ·
2555
+ * server_unreachable`. `offline` = the device has no network (`navigator.onLine` false);
2556
+ * `server_unreachable` = online, but our API or the media server does not answer the probe.
2557
+ * The two get different screens because they have different fixes.
2558
+ * 2. **The banner debounce** (`reconnect_banner_delay_ms`): the region is honest at once (the
2559
+ * contract attribute), the banner appears only if the blip outlasts the delay (`net-blip`).
2560
+ * 3. **Auto-rejoin after the SDK gives up** (`auto_rejoin`, `auto_rejoin_max_attempts`): from
2561
+ * `disconnected`, when the network is back AND the probe answers, rejoin with a fresh mint into
2562
+ * the same identity — the server restores role / hand / admission from the person row. Attempts
2563
+ * are only spent while reachable; a long offline spell costs none. Manual Rejoin always works.
2564
+ * 4. **The reload-after-offline marker**: while in the call (or dropped from it) the tab keeps a
2565
+ * per-tab marker, so a page that Chrome replaced with its own offline page and then reloaded
2566
+ * comes back INTO the call instead of to the pre-join (`resume`). A deliberate exit clears it.
2567
+ * 5. **The jittered reconnect policy** handed to LiveKit (`createReconnectPolicy`): jitter from
2568
+ * attempt 0 (the SDK's default has none on its first two attempts — 650 people reconnecting
2569
+ * on the same millisecond is the thundering-herd issue), with the give-up limits as options.
2570
+ *
2571
+ * Others' drops (`net-others-see-drop`) are the pure ghost helpers at the bottom: an unannounced
2572
+ * departure keeps a `reconnecting` tile for `reconnect_grace_seconds`.
2573
+ */
2574
+
2575
+ /** The server-delivered connection knobs (CORE-DESIGN §4.2/§4.3), resolved per meeting. */
2576
+ interface ConnectionPolicy {
2577
+ /** `reconnect_banner_delay_ms`: how long a drop must last before the banner shows. */
2578
+ readonly bannerDelayMs: number;
2579
+ /** `auto_rejoin`: rejoin by itself after the SDK gives up. */
2580
+ readonly autoRejoin: boolean;
2581
+ /** `auto_rejoin_max_attempts`: rejoin attempts (while reachable) before only Rejoin is left. */
2582
+ readonly autoRejoinMaxAttempts: number;
2583
+ /** `reconnect_grace_seconds`: how long others keep a dropped person's `reconnecting` tile. */
2584
+ readonly reconnectGraceSeconds: number;
2585
+ }
2586
+ /** The knob defaults (§4.3) — what an older server that sends nothing gets. */
2587
+ declare const DEFAULT_CONNECTION_POLICY: ConnectionPolicy;
2588
+ /** The token's `connection_policy` object → the typed policy; anything missing is the default. */
2589
+ declare function connectionPolicyFrom(raw: unknown): ConnectionPolicy;
2590
+ /** LiveKit's `ReconnectPolicy` shape, restated so this module never imports the SDK. */
2591
+ interface ReconnectPolicyLike {
2592
+ nextRetryDelayInMs(context: {
2593
+ readonly retryCount: number;
2594
+ readonly elapsedMs: number;
2595
+ }): number | null;
2596
+ }
2597
+ interface ReconnectPolicyOptions {
2598
+ /** Base delay per attempt (ms); the last one repeats. Default: LiveKit's own schedule. */
2599
+ readonly delaysMs?: readonly number[];
2600
+ /** Stop after this long since the drop (ms). Default 45 000 — about the SDK's own ~44 s. */
2601
+ readonly giveUpMs?: number;
2602
+ /** Stop after this many attempts. Default 12. */
2603
+ readonly maxAttempts?: number;
2604
+ readonly random?: () => number;
2605
+ }
2606
+ /** livekit-client 2.22 `DEFAULT_RETRY_DELAYS_IN_MS` (verified in the SDK source, 2026-10-08). */
2607
+ declare const LIVEKIT_BASE_DELAYS_MS: readonly number[];
2608
+ /**
2609
+ * THE JITTERED RECONNECT POLICY. Every attempt — attempt 0 included — gets a random spread of at
2610
+ * least 500 ms (half the base delay, capped at 2 s), so a room whose SFU blinks does not hear
2611
+ * every client come back on the same millisecond.
2612
+ */
2613
+ declare function createReconnectPolicy(options?: ReconnectPolicyOptions): ReconnectPolicyLike;
2614
+ /** Whether the device has a network, and when that changes (the platform port's `network`). */
2615
+ interface NetworkPort {
2616
+ online(): boolean;
2617
+ subscribe(listener: (online: boolean) => void): () => void;
2618
+ }
2619
+ /** `navigator.onLine` + `online`/`offline` events; outside a browser it is always online. */
2620
+ declare function createWebNetworkPort(): NetworkPort;
2621
+ /**
2622
+ * THE REACHABILITY PROBE: does each URL answer at all? An opaque (`no-cors`) GET resolves for any
2623
+ * HTTP answer and rejects only when the network path is down — exactly the question, with no
2624
+ * CORS dependency. All URLs must answer; a timeout is a "no".
2625
+ */
2626
+ declare function createReachabilityProbe(urls: () => readonly string[], timeoutMs?: number): () => Promise<boolean>;
2627
+ /** `wss://x.livekit.cloud` → `https://x.livekit.cloud` (what the probe can GET). */
2628
+ declare function httpUrlOf(serverUrl: string): string;
2629
+ /** Per-tab storage for the reload-after-offline marker (session storage on the web). */
2630
+ interface ResumeStorage {
2631
+ load(roomName: string): ResumeMarker | null;
2632
+ save(roomName: string, marker: ResumeMarker): void;
2633
+ clear(roomName: string): void;
2634
+ }
2635
+ interface ResumeMarker {
2636
+ readonly request: JoinRequest;
2637
+ /** Epoch ms of the last time this tab was in (or dropped from) the call. */
2638
+ readonly at: number;
2639
+ }
2640
+ /** How long after the last in-call moment a reload still goes straight back in. */
2641
+ declare const RESUME_WINDOW_MS: number;
2642
+ declare function createWebResumeStorage(): ResumeStorage | undefined;
2643
+ interface ConnectionSupervisorOptions {
2644
+ readonly store: MeetStore;
2645
+ readonly network: NetworkPort;
2646
+ /** Do our API and the media server answer? */
2647
+ readonly probe: () => Promise<boolean>;
2648
+ readonly policy: () => ConnectionPolicy;
2649
+ /** Rejoin from `disconnected` with a fresh mint (the engine's `autoRejoin`). */
2650
+ readonly autoRejoin: () => Promise<void>;
2651
+ /** The request to resume with (current media state folded in), or null. */
2652
+ readonly currentRequest: () => JoinRequest | null;
2653
+ readonly resume?: ResumeStorage | undefined;
2654
+ readonly now?: () => number;
2655
+ readonly random?: () => number;
2656
+ /** How often to re-probe while not stable (ms). Default 3000. */
2657
+ readonly probeEveryMs?: number;
2658
+ readonly report?: (level: "info" | "warn", message: string) => void;
2659
+ }
2660
+ interface ConnectionSupervisor {
2661
+ /** LiveKit's connection state, as the driver reports it. */
2662
+ driverState(state: DriverConnectionState): void;
2663
+ /** The region to show beside `disconnected` when the server's state read failed. */
2664
+ exitConnection(): Exclude<ConnectionRegion, "stable" | "reconnecting" | "signal_reconnecting">;
2665
+ /** The resume marker for `roomName` if it is fresh, else null (and a stale one is cleared). */
2666
+ resumable(roomName: string): ResumeMarker | null;
2667
+ dispose(): void;
2668
+ }
2669
+ /** Backoff before auto-rejoin attempt `n` (0-based): 1 s, 2 s, 4 s … capped at 15 s, jittered ±50%. */
2670
+ declare function autoRejoinDelayMs(attempt: number, random?: () => number): number;
2671
+ declare function createConnectionSupervisor(options: ConnectionSupervisorOptions): ConnectionSupervisor;
2672
+ declare function isUnannouncedDeparture(reason: string | null): boolean;
2673
+ interface Ghost {
2674
+ readonly participant: DriverParticipant;
2675
+ readonly until: number;
2676
+ }
2677
+ /**
2678
+ * The roster with the live ghosts merged in. A ghost whose identity is back in the live roster is
2679
+ * dropped (the person returned — one tile, never a copy); an expired one is dropped.
2680
+ */
2681
+ declare function mergeGhosts(live: readonly DriverParticipant[], ghosts: ReadonlyMap<string, Ghost>, at: number): {
2682
+ readonly roster: readonly (DriverParticipant & {
2683
+ readonly reconnecting?: boolean;
2684
+ })[];
2685
+ readonly kept: Map<string, Ghost>;
2686
+ };
2687
+
2157
2688
  /**
2158
2689
  * THE TOKEN FLOW — the ONLY authorization path into a room (R1).
2159
2690
  *
@@ -2228,6 +2759,12 @@ interface RoomToken {
2228
2759
  readonly breakoutRoomId?: string | null | undefined;
2229
2760
  /** HR-360 wave 2 (MD-16): who sees the "Observing" line in People (knob). */
2230
2761
  readonly observersVisibleTo?: ObserversVisibleTo | undefined;
2762
+ /**
2763
+ * S6b: the meeting's connection knobs (`reconnect_banner_delay_ms`, `auto_rejoin`,
2764
+ * `auto_rejoin_max_attempts`, `reconnect_grace_seconds`), resolved by the server's policy
2765
+ * ladder. Absent from an older server → the knob defaults.
2766
+ */
2767
+ readonly connectionPolicy?: ConnectionPolicy | undefined;
2231
2768
  }
2232
2769
 
2233
2770
  interface TokenClientOptions {
@@ -2902,167 +3439,6 @@ declare function pushMeetingToGoogleCalendar(api: MeetApi, meetingId: MeetingId,
2902
3439
  calendarId?: string;
2903
3440
  }): Promise<Record<string, unknown>>;
2904
3441
 
2905
- /**
2906
- * THE ROOM ENGINE — the object a host mounts once and never reasons about.
2907
- *
2908
- * It owns every sequence a calling product gets wrong: ask for a token, connect,
2909
- * project the roster, carry the in-room control plane, keep the phase honest
2910
- * through a reconnect, and tear everything down exactly once. A consumer's job
2911
- * is `engine.join(...)` and rendering a snapshot.
2912
- *
2913
- * THREE rules are structural here, not documented-and-hoped:
2914
- *
2915
- * 1. **The phase never lies.** `connecting` becomes `connected` only when the
2916
- * driver says so, and any failure becomes `failed` WITH the remedy attached
2917
- * to the snapshot. There is no path that leaves a spinner running forever —
2918
- * the single most common complaint about every video product.
2919
- * 2. **Role comes from the TOKEN, never from the client.** A participant's role
2920
- * is read out of the server-minted metadata. A client that could decide it
2921
- * was host could mute anyone; here the claim is unforgeable because the
2922
- * server signed it.
2923
- * 3. **Teardown is idempotent and total.** Leaving twice, or unmounting during
2924
- * a connect, disconnects once and leaves no listener behind. The half-torn
2925
- * room is what produces "my camera light stays on after the call".
2926
- */
2927
-
2928
- interface JoinRequest {
2929
- readonly roomName: RoomName;
2930
- readonly meetingId?: string | undefined;
2931
- readonly slug?: string | undefined;
2932
- /** `audio` joins with the camera off. Same room, same code path (D3). */
2933
- readonly mode: CallMode;
2934
- /**
2935
- * Whether to ATTEMPT the microphone on join. Defaults to true. `false` is a
2936
- * deliberate listen-only join — the pre-join screen's "Join with microphone"
2937
- * toggle. Either way the join itself never depends on the outcome; a refused
2938
- * device lands in `snapshot.media.microphone.unavailable`, not in a throw.
2939
- */
2940
- readonly microphone?: boolean | undefined;
2941
- /** Meet wave 5: a breakout room of this meeting; absent/null = the main room. */
2942
- readonly breakoutRoomId?: string | null | undefined;
2943
- /** HR-360 wave 2 (MD-16): ask to join as a silent observer. The server decides. */
2944
- readonly observe?: boolean | undefined;
2945
- }
2946
- interface RoomEngineOptions {
2947
- store: MeetStore;
2948
- tokens: TokenClient;
2949
- identity: MeetIdentity;
2950
- driverFactory: RoomDriverFactory;
2951
- /** Canonical public metadata read; room packets alone never authorize teardown. */
2952
- resolveMeeting?: ((slug: string) => Promise<MeetingRecord>) | undefined;
2953
- onDiagnostic?: ((event: {
2954
- level: "info" | "warn" | "error";
2955
- message: string;
2956
- remedy?: string;
2957
- }) => void) | undefined;
2958
- /** Fired when the local participant leaves for any reason, including kicked. */
2959
- onLeft?: ((reason: string | null) => void) | undefined;
2960
- now?: (() => number) | undefined;
2961
- /** Remembered noise suppression / background choice (Meet wave 2). */
2962
- effectsStorage?: EffectsStorage | undefined;
2963
- /**
2964
- * The device the person PICKED for each kind (the pre-join pickers, the
2965
- * settings panel). Applied before any track is created — through 0.7.x the
2966
- * pickers stored a choice nothing ever read, so every join used the browser
2967
- * default whatever the screen said.
2968
- */
2969
- resolveDevice?: ((kind: "audioinput" | "videoinput" | "audiooutput") => string | null) | undefined;
2970
- /**
2971
- * §3.4 EXIT-REASON RESOLUTION: on every LiveKit disconnect the engine asks the server's state
2972
- * read what happened to THIS connection (`GET /v1/meet/state?connection=`). Resolves null when
2973
- * the read failed (offline / unreachable), undefined when this room has no state read (a call).
2974
- */
2975
- readExit?: ((connection: string) => Promise<ExitServerWord | null | undefined>) | undefined;
2976
- /** Was the page frozen when the connection dropped (Page Lifecycle)? → `inactive`. */
2977
- wasFrozen?: (() => boolean) | undefined;
2978
- }
2979
- interface RoomEngine {
2980
- join(request: JoinRequest): Promise<void>;
2981
- /**
2982
- * 🚨 THE HOST SAID YES — COME IN.
2983
- *
2984
- * A lobby token is minted with `canPublish: false` and `canSubscribe: false`
2985
- * (that is what makes the lobby real rather than a screen over a live
2986
- * microphone), so admission is not a UI state change: the cached token has to
2987
- * be thrown away and a NEW one minted with the grants the server now gives.
2988
- * Without this the waiting room's own sentence — "you will join automatically
2989
- * once you are admitted" — is a lie, and the guest waits forever.
2990
- */
2991
- rejoin(): Promise<void>;
2992
- /**
2993
- * Meet wave 5: go to a breakout room (`null` = back to the main room). The server
2994
- * mints the pass (`/token` with `breakout_room_id`) and refuses a room the plan does
2995
- * not allow; the move is a reconnect that never reads as leaving the meeting.
2996
- */
2997
- moveToBreakout(roomId: string | null): Promise<void>;
2998
- leave(): Promise<void>;
2999
- /** The person's own "back to the device check" press from `left` / `disconnected`. */
3000
- backToPrejoin(): void;
3001
- /** Clear a refused-share notice (the `share` region) the person dismissed. */
3002
- dismissShareNotice(): void;
3003
- setMicrophoneEnabled(enabled: boolean): Promise<void>;
3004
- setCameraEnabled(enabled: boolean): Promise<void>;
3005
- setScreenShareEnabled(enabled: boolean): Promise<void>;
3006
- raiseHand(raised: boolean): Promise<void>;
3007
- react(emoji: string): Promise<void>;
3008
- /**
3009
- * A PRIVATE message to one person (Meet wave 2). Data plane only, addressed
3010
- * to that identity, NEVER saved — see `core/chat.ts` for the saved,
3011
- * everyone-chat, which goes through the server.
3012
- */
3013
- sendPrivateChat(to: ParticipantIdentity, text: string): Promise<void>;
3014
- /**
3015
- * Change this participant's own media processing — noise suppression and the
3016
- * camera background — on the live tracks, and remember it in this browser.
3017
- * A refused effect is a diagnostic and a revert, never a lost camera.
3018
- */
3019
- setEffects(next: Partial<MediaEffects>): Promise<void>;
3020
- /** Whether this browser can run a background effect at all (absent, never dead). */
3021
- supportsVideoEffects(): boolean;
3022
- /** Whether this browser can share a screen at all — the control is ABSENT where it cannot. */
3023
- supportsScreenShare(): boolean;
3024
- /** Choose the speaker (audio output) for this meeting. */
3025
- switchDevice(kind: "audioinput" | "videoinput" | "audiooutput", deviceId: string): Promise<void>;
3026
- pin(identity: ParticipantIdentity | null): void;
3027
- setLayout(layout: StageLayout): void;
3028
- attach(identity: ParticipantIdentity, kind: "camera" | "microphone" | "screen", element: HTMLMediaElement): () => void;
3029
- token(): RoomToken | null;
3030
- dispose(): Promise<void>;
3031
- }
3032
- /** Where this browser keeps a participant's media choices. */
3033
- interface EffectsStorage {
3034
- load(): MediaEffects;
3035
- save(effects: MediaEffects): void;
3036
- }
3037
- /**
3038
- * 🚨 THE SERVER'S WORD, ON TWO CARRIERS (MRI-A11, feedback `7418ef78`).
3039
- *
3040
- * aidream mints the role into the token's participant METADATA (a JSON blob)
3041
- * AND as a LiveKit participant ATTRIBUTE. Both come out of one decision in
3042
- * `services/meet/service.py`; neither can be written by a client. The attribute
3043
- * is preferred because LiveKit delivers the attribute map on every roster
3044
- * update, while a roster event can carry an empty metadata string — which is
3045
- * precisely how, through 2026-09-08, EVERY participant projected as
3046
- * "participant" and no host ever saw a host control in a browser.
3047
- *
3048
- * These strings are pinned on the server side too: `test_meet_service.py` reads
3049
- * them out of THIS file and fails if the two sides drift.
3050
- */
3051
- declare const PARTICIPANT_ROLE_ATTRIBUTE = "matrx.participant.role";
3052
- declare const PARTICIPANT_CAN_RECORD_ATTRIBUTE = "matrx.participant.can_record";
3053
- /** Standing travels beside the role (CORE-DESIGN §2.1). Pinned by aidream's tests too. */
3054
- declare const PARTICIPANT_STANDING_ATTRIBUTE = "matrx.participant.standing";
3055
- /**
3056
- * Metadata is SERVER-MINTED and therefore trusted; it is still parsed
3057
- * defensively, because a malformed blob must degrade a tile to "participant"
3058
- * rather than throw inside a roster event and freeze the whole grid.
3059
- */
3060
- declare function projectParticipant(driver: DriverParticipant, joinedAt: string): MeetParticipant;
3061
- declare function createRoomEngine(options: RoomEngineOptions): RoomEngine;
3062
- /** Exported for the host-power check in UI code — one definition, one rank. */
3063
- declare function canModerate(participant: MeetParticipant | undefined): boolean;
3064
- declare function hostUserIds(participants: readonly MeetParticipant[]): readonly UserId[];
3065
-
3066
3442
  /**
3067
3443
  * A PARTICIPANT'S OWN MEDIA CHOICES, REMEMBERED (Meet wave 2).
3068
3444
  *
@@ -3394,8 +3770,12 @@ interface ObserveInput {
3394
3770
  /** The row the consumer resolved, when the store has none yet (the guest lane). */
3395
3771
  readonly meeting?: MeetingRecord | null | undefined;
3396
3772
  }
3397
- /** The store's device state → the contract's `camera` / `microphone` value. */
3398
- declare function observedDevice(track: LocalTrackState): string;
3773
+ /**
3774
+ * The store's device state → the contract's `camera` / `microphone` value (CORE-DESIGN §3.2).
3775
+ * The reason comes from the media supervisor (S6c), which reads the browser's permission store: a
3776
+ * kind the browser denies is `unavailable:denied` even when nothing tried to open it.
3777
+ */
3778
+ declare function observedDevice(track: LocalTrackState, permission?: PermissionReading): string;
3399
3779
  /**
3400
3780
  * The phase the person is looking at: the store's phase, verbatim (CORE-DESIGN §3.1). The one
3401
3781
  * derivation left is the ended meeting: a row that says it is over renders `ended` for anyone not
@@ -3412,6 +3792,6 @@ declare function observedQuality(quality: ConnectionQuality): string | null;
3412
3792
  /** `[data-meet-participant]` attributes for one person's tile or roster row. */
3413
3793
  declare function participantAttributes(p: MeetParticipant): Record<string, string>;
3414
3794
  /** The listen-only notice's contract code, from why the microphone is off. */
3415
- declare function microphoneNoticeCode(track: LocalTrackState): "mic-blocked" | "device-lost" | null;
3795
+ declare function microphoneNoticeCode(track: LocalTrackState, permission?: PermissionReading): "mic-blocked" | "device-lost" | null;
3416
3796
 
3417
- export { ATTENDANCE_CSV_HEADER, AdmissionState, type AnnounceResult, type ApplyRoomEventArgs, type AskArgs, type AttendanceKind, type AttendanceRow, type AudioProcessing, CALL_INVITE_KIND, CALL_INVITE_VERSION, CONSENT_NOTICE_ATTRIBUTE, type CallCenter, type CallCenterOptions, CallId, CallInvite, type CallInviteHandlerOptions, type CallInvitePayload, CallMode, type CallOutcomePhase, CallState, type CollabAction, CollabPanel, type CollabRequest, type CollabResult, type CollabRuntime, CollabSnapshot, type CollabStoreLike, ConnectionQuality, type ConnectionRegion, type CreateCallInviteInput, type CreateMeetingInput, DATA_TOPICS, DEFAULT_EFFECTS, DEFAULT_PAGE_SIZE, type DataTopic, type DeviceKind, type DeviceManager, type DeviceManagerOptions, type DeviceSnapshot, type DeviceStorage, type DriverConnectionState, type DriverParticipant, type DriverTrackKind, type DriverTranscription, END_MEETING_NOT_SERVED, type EffectsStorage, type EndMeetingResult, type ExitInput, type ExitPhase, type ExitResolution, type ExitServerWord, type FollowUpDraft, type FollowUpRecipient, type FollowUpSendResult, GUEST_RECORD_ACCESS_CHOICES, GUEST_RECORD_ACCESS_LEVELS, GUEST_RECORD_ACCESS_METADATA_KEY, GUEST_RECORD_NOT_SHARED, type GuestRecordAccess, type GuestRecordAccessResolution, type GuestRecordAccessSetting, IDLE_MEDIA, IDLE_PHASES, IDLE_TRACK, type InRoomClient, type InRoomClientOptions, InviteeInput, InviteeRole, JOIN_REFUSAL_REASONS, type JoinRefusalReason, type JoinRequest, LISTEN_ONLY_NOTICE, LIVE_PHASES, type LibraryRecording, type LiveKitDriverOptions, LobbyEntry, type LocalMediaState, type LocalTrackState, MEETING_INVITE_KIND, MEETING_INVITE_VERSION, MEETING_PASS_HEADER, MEETING_RECORD_GRACE_MS, MEET_ANSWER_CONTEXT_EVENT, MEET_ANSWER_EVENTS, MEET_EVENTS, MEET_REACTIONS, MEET_ROUTES, MEET_SCHEMA, MODERATION_ACTIONS, type MediaDevicesLike, type MediaEffects, type MediaUnavailable, type MeetAi, type MeetAiAnswer, type MeetAiOptions, type MeetAnswerCoverage, MeetApi, type MeetDevice, MeetError, type MeetErrorCode, type MeetEvent, type MeetGrants, MeetIdentity, type MeetObservation, MeetParticipant, type MeetPatch, type MeetPhase, type MeetRepository, type MeetRoomDriver, type MeetSnapshot, type MeetStore, MeetingAttendee, MeetingChanges, type MeetingChatHistory, MeetingId, type MeetingInviteHandlerOptions, type MeetingInvitePayload, MeetingInvitee, MeetingKind, MeetingNote, MeetingOccurrence, MeetingRecord, type MeetingRecordAnswer, type MeetingRecordBundle, type MeetingRecordCache, type MeetingRecordChange, type MeetingRecordFailure, type MeetingRecordParts, type MeetingRecordRestriction, type MeetingRecordRestrictionNotice, type MeetingRecordState, type MeetingSearchHit, type MeetingSearchSource, type MeetingSession, type MeetingSessionOptions, MeetingSettings, type ModerationAction, type ModerationNotice, type ModerationRequest, type ModerationResult, NOTE_TAKER_IDENTITY_PREFIX, NOTE_TAKER_METADATA_KEY, NO_GRANTS, NO_SHARE, type NameBook, type NoteTakerDispatch, type NoteTakerRefusal, NoteTakerState, type NoteTakerStatus, type ObserveInput, type ObservedConnection, type ObservedPhase, ObserversVisibleTo, OccurrenceOverride, OrganizationId, PARTICIPANT_CAN_RECORD_ATTRIBUTE, PARTICIPANT_ROLE_ATTRIBUTE, PARTICIPANT_STANDING_ATTRIBUTE, ParticipantIdentity, ParticipantRole, ParticipantStanding, type PhaseEvent, type PhaseEventType, REACTION_TTL_MS, REFUSED_ABSENCE, ROOM_EVENTS, ROOM_EVENT_PREFIX, RPCS, type RecordingController, type RecordingControllerOptions, RecordingPolicy, RecordingRecord, RecordingState, type RepositoryOptions, type RoomChatMessage, type RoomDriverFactory, type RoomDriverHandlers, type RoomEngine, type RoomEngineOptions, type RoomEventOutcome, type RoomEventTopic, RoomName, type RoomReaction, type RoomToken, type RoomTokenStorage, RsvpAnswer, RsvpInvitation, type ScheduleMeetingInput, type ShareRegion, type Stage, type StageArgs, StageLayout, TABLES, TRANSCRIPTION_ATTRIBUTES, TRANSCRIPTION_TOPIC, type TokenClient, type TokenClientOptions, type TokenRequest, type TranscriptBlock, type TranscriptFormat, type TranscriptMatch, TranscriptSegment, UpcomingOccurrence, UserId, type VideoEffect, WAITING_PHASES, WrapUpAnnouncement, type WrapUpStatus, absenceSentence, activeWrapUpSummary, activityAttention, admissionPhase, announceMeeting, applyRoomEvent, assembleMeetingRecord, attendanceCsv, attendanceReport, attendanceTotals, attendeeKey, availableActivities, callInviteAction, callInviteText, callStateLabel, canModerate, canModerateParticipant, canTransition, captionSegment, changesPatch, clockPosition, computeStage, countLabel, createCallCenter, createCallInviteHandler, createDeviceManager, createInRoomClient, createLiveKitDriver, createMeetAi, createMeetRepository, createMeetStore, createMeetingInviteHandler, createMeetingRecordCache, createMeetingSession, createMemoryDeviceStorage, createRecordingController, createRoomEngine, createTokenClient, createWebDeviceStorage, createWebEffectsStorage, createWebRoomTokenStorage, displayNameFor, gridColumns, groupTranscript, guestRecordAccessExplanation, guestRecordAccessLabel, guestRecordAccessSetting, hasEnded, hostUserIds, invalidResponse, isGuestIdentity, isJoinRefusalReason, isListenOnly, isLivePhase, isNoteTaker, isRealName, isRefusedPhase, isRoomEventTopic, isWaitingPhase, learnName, learnNames, listenOnlyNotice, loadFollowUpDraft, loadGuestMeetingRecord, loadMeetingChat, loadMeetingRecord, loadMeetingRecordParts, mapConnectionState, mapQuality, meetingInviteAction, meetingLength, meetingLink, meetingPassHeaders, meetingPath, meetingSlug, meetingTopic, meetingWhen, mergeCaption, mergeRecord, microphoneNoticeCode, moveToRoom, nextPhase, nextTranslationBatch, normalizeDeviceError, normalizeMeetError, normalizeSpoken, noteTakerIdentity, noteTakerLabel, noteTakerParticipant, noteTakerStatus, notesDocument, observe as observeMeeting, observedDevice, observedPhase, observedQuality, orderParticipants, parseRoomToken, participantAttributes, participantSummary, performCollab, playableRecordingFileId, projectInvitee, projectLibraryRecording, projectNote, projectOccurrence, projectOverride, projectParticipant, projectRsvpInvitation, projectSearchHit, projectSegment, projectUpcoming, pushMeetingToGoogleCalendar, readServerRefusal, readableTranscript, reasonOf, recordingLabel, recordingNotice, recordingOffsetMs, refusalPhase, rememberedMeetingPass, resolveExit, roleOf, roomTopic, rootAttributes, searchTranscript, sendFollowUp, setCaptionLanguage, setGuestRecordAccess, settingsPatch, snapshotParticipant, snippetRuns, staticObservation, summarizeMeetingRecordRestrictions, takeMoveRequest, takePanelRequest, transcriptDocument, unavailableFrom, uniqueAttendees, untranslatedLines, userTopic, wasRefused, wrapUpStatus };
3797
+ export { ATTENDANCE_CSV_HEADER, AdmissionState, type AnnounceResult, type ApplyRoomEventArgs, type AskArgs, type AttendanceKind, type AttendanceRow, type AudioProcessing, type BrowserFamily, CALL_INVITE_KIND, CALL_INVITE_VERSION, CONSENT_NOTICE_ATTRIBUTE, type CallCenter, type CallCenterOptions, CallId, CallInvite, type CallInviteHandlerOptions, type CallInvitePayload, CallMode, type CallOutcomePhase, CallState, type CollabAction, CollabPanel, type CollabRequest, type CollabResult, type CollabRuntime, CollabSnapshot, type CollabStoreLike, type ConnectionPolicy, ConnectionQuality, type ConnectionRegion, type ConnectionSupervisor, type ConnectionSupervisorOptions, type CreateCallInviteInput, type CreateMeetingInput, DATA_TOPICS, DEFAULT_CONNECTION_POLICY, DEFAULT_EFFECTS, DEFAULT_PAGE_SIZE, type DataTopic, type DeviceKind, type DeviceManager, type DeviceManagerOptions, type DeviceNotice, type DeviceOffer, type DeviceSnapshot, type DeviceStorage, type DeviceUnavailableReason, type DriverConnectionState, type DriverParticipant, type DriverTrackKind, type DriverTranscription, END_MEETING_NOT_SERVED, type EffectsStorage, type EndMeetingResult, type ExitInput, type ExitPhase, type ExitResolution, type ExitServerWord, type FollowUpDraft, type FollowUpRecipient, type FollowUpSendResult, GUEST_RECORD_ACCESS_CHOICES, GUEST_RECORD_ACCESS_LEVELS, GUEST_RECORD_ACCESS_METADATA_KEY, GUEST_RECORD_NOT_SHARED, type Ghost, type GuestRecordAccess, type GuestRecordAccessResolution, type GuestRecordAccessSetting, IDLE_MEDIA, IDLE_MEDIA_REGION, IDLE_PHASES, IDLE_TRACK, type InRoomClient, type InRoomClientOptions, InviteeInput, InviteeRole, JOIN_REFUSAL_REASONS, type JoinRefusalReason, type JoinRequest, LISTEN_ONLY_NOTICE, LIVEKIT_BASE_DELAYS_MS, LIVE_PHASES, type LibraryRecording, type LiveKitDriverOptions, LobbyEntry, type LocalMediaState, type LocalTrackState, MEETING_INVITE_KIND, MEETING_INVITE_VERSION, MEETING_PASS_HEADER, MEETING_RECORD_GRACE_MS, MEET_ANSWER_CONTEXT_EVENT, MEET_ANSWER_EVENTS, MEET_EVENTS, MEET_REACTIONS, MEET_ROUTES, MEET_SCHEMA, MODERATION_ACTIONS, type MediaDevicesLike, type MediaEffects, type MediaKind, type MediaPlatform, type MediaRegionState, type MediaSupervisor, type MediaSupervisorOptions, type MediaUnavailable, type MeetAi, type MeetAiAnswer, type MeetAiOptions, type MeetAnswerCoverage, MeetApi, type MeetDevice, MeetError, type MeetErrorCode, type MeetEvent, type MeetGrants, MeetIdentity, type MeetObservation, MeetParticipant, type MeetPatch, type MeetPhase, type MeetRepository, type MeetRoomDriver, type MeetSnapshot, type MeetStore, MeetingAttendee, MeetingChanges, type MeetingChatHistory, MeetingId, type MeetingInviteHandlerOptions, type MeetingInvitePayload, MeetingInvitee, MeetingKind, MeetingNote, MeetingOccurrence, MeetingRecord, type MeetingRecordAnswer, type MeetingRecordBundle, type MeetingRecordCache, type MeetingRecordChange, type MeetingRecordFailure, type MeetingRecordParts, type MeetingRecordRestriction, type MeetingRecordRestrictionNotice, type MeetingRecordState, type MeetingSearchHit, type MeetingSearchSource, type MeetingSession, type MeetingSessionOptions, MeetingSettings, type ModerationAction, type ModerationNotice, type ModerationRequest, type ModerationResult, NOTE_TAKER_IDENTITY_PREFIX, NOTE_TAKER_METADATA_KEY, NO_GRANTS, NO_SHARE, type NameBook, type NetworkPort, type NoteTakerDispatch, type NoteTakerRefusal, NoteTakerState, type NoteTakerStatus, type ObserveInput, type ObservedConnection, type ObservedPhase, ObserversVisibleTo, OccurrenceOverride, OrganizationId, type OsFamily, PARTICIPANT_CAN_RECORD_ATTRIBUTE, PARTICIPANT_ROLE_ATTRIBUTE, PARTICIPANT_STANDING_ATTRIBUTE, ParticipantIdentity, ParticipantRole, ParticipantStanding, type PermissionReading, type PhaseEvent, type PhaseEventType, type PlatformDevice, REACTION_TTL_MS, REFUSED_ABSENCE, RESUME_WINDOW_MS, ROOM_EVENTS, ROOM_EVENT_PREFIX, RPCS, type ReconnectPolicyLike, type ReconnectPolicyOptions, type RecordingController, type RecordingControllerOptions, RecordingPolicy, RecordingRecord, RecordingState, type RepositoryOptions, type ResumeMarker, type ResumeStorage, type RoomChatMessage, type RoomDriverFactory, type RoomDriverHandlers, type RoomEngine, type RoomEngineOptions, type RoomEventOutcome, type RoomEventTopic, RoomName, type RoomReaction, type RoomToken, type RoomTokenStorage, RsvpAnswer, RsvpInvitation, type ScheduleMeetingInput, type ShareRegion, type Stage, type StageArgs, StageLayout, TABLES, TRANSCRIPTION_ATTRIBUTES, TRANSCRIPTION_TOPIC, type TokenClient, type TokenClientOptions, type TokenRequest, type TranscriptBlock, type TranscriptFormat, type TranscriptMatch, TranscriptSegment, UpcomingOccurrence, UserId, type VideoEffect, WAITING_PHASES, WrapUpAnnouncement, type WrapUpStatus, absenceSentence, activeWrapUpSummary, activityAttention, admissionPhase, announceMeeting, applyRoomEvent, assembleMeetingRecord, attendanceCsv, attendanceReport, attendanceTotals, attendeeKey, autoRejoinDelayMs, availableActivities, callInviteAction, callInviteText, callStateLabel, canModerate, canModerateParticipant, canTransition, captionSegment, changesPatch, classifyDeviceFailure, clockPosition, computeStage, connectionPolicyFrom, countLabel, createCallCenter, createCallInviteHandler, createConnectionSupervisor, createDeviceManager, createInRoomClient, createLiveKitDriver, createMediaSupervisor, createMeetAi, createMeetRepository, createMeetStore, createMeetingInviteHandler, createMeetingRecordCache, createMeetingSession, createMemoryDeviceStorage, createReachabilityProbe, createReconnectPolicy, createRecordingController, createRoomEngine, createTokenClient, createWebDeviceStorage, createWebEffectsStorage, createWebNetworkPort, createWebResumeStorage, createWebRoomTokenStorage, detectBrowser, detectOs, deviceChipLabel, deviceFixSteps, deviceNoticeCode, deviceUnavailableReason, displayNameFor, gridColumns, groupTranscript, guestRecordAccessExplanation, guestRecordAccessLabel, guestRecordAccessSetting, hasEnded, hostUserIds, httpUrlOf, invalidResponse, isGuestIdentity, isJoinRefusalReason, isListenOnly, isLivePhase, isNoteTaker, isRealName, isRefusedPhase, isRoomEventTopic, isUnannouncedDeparture, isWaitingPhase, learnName, learnNames, listenOnlyNotice, loadFollowUpDraft, loadGuestMeetingRecord, loadMeetingChat, loadMeetingRecord, loadMeetingRecordParts, mapConnectionState, mapQuality, meetingInviteAction, meetingLength, meetingLink, meetingPassHeaders, meetingPath, meetingSlug, meetingTopic, meetingWhen, mergeCaption, mergeGhosts, mergeRecord, microphoneNoticeCode, moveToRoom, nextPhase, nextTranslationBatch, normalizeDeviceError, normalizeMeetError, normalizeSpoken, noteTakerIdentity, noteTakerLabel, noteTakerParticipant, noteTakerStatus, notesDocument, observe as observeMeeting, observedDevice, observedPhase, observedQuality, orderParticipants, parseRoomToken, participantAttributes, participantSummary, performCollab, playableRecordingFileId, projectInvitee, projectLibraryRecording, projectNote, projectOccurrence, projectOverride, projectParticipant, projectRsvpInvitation, projectSearchHit, projectSegment, projectUpcoming, pushMeetingToGoogleCalendar, readServerRefusal, readableTranscript, reasonOf, recordingLabel, recordingNotice, recordingOffsetMs, refusalPhase, rememberedMeetingPass, resolveExit, roleOf, roomTopic, rootAttributes, searchTranscript, sendFollowUp, setCaptionLanguage, setGuestRecordAccess, settingsPatch, snapshotParticipant, snippetRuns, staticObservation, summarizeMeetingRecordRestrictions, takeMoveRequest, takePanelRequest, transcriptDocument, unavailableFrom, uniqueAttendees, untranslatedLines, userTopic, wasRefused, webMediaPlatform, wrapUpStatus };