@ai-matrx/meet 0.7.6 → 0.7.7

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/index.d.cts CHANGED
@@ -1342,6 +1342,10 @@ declare const RPCS: {
1342
1342
  readonly meetingOccurrences: "meet_meeting_occurrences";
1343
1343
  /** Every occurrence the reader can see between two instants — the Upcoming list. */
1344
1344
  readonly occurrencesBetween: "meet_occurrences_between";
1345
+ /** The recordings library. */
1346
+ readonly myRecordings: "meet_my_recordings";
1347
+ /** Titles, agendas, current notes and final transcript lines; snippets mark hits with ⟦ ⟧. */
1348
+ readonly search: "meet_search";
1345
1349
  };
1346
1350
  type Row = Record<string, unknown>;
1347
1351
  interface CreateCallInviteInput {
@@ -1428,6 +1432,17 @@ interface MeetRepository {
1428
1432
  acknowledgedAt: string;
1429
1433
  }): Promise<void>;
1430
1434
  recordings(meetingId: MeetingId): Promise<readonly RecordingRecord[]>;
1435
+ /** Meet wave 3: every recording of the reader's own meetings, newest first. */
1436
+ myRecordings(args?: {
1437
+ includeArchived?: boolean;
1438
+ limit?: number;
1439
+ }): Promise<readonly LibraryRecording[]>;
1440
+ /** Meet wave 3: search the reader's own meetings. */
1441
+ searchMeetings(query: string, limit?: number): Promise<readonly MeetingSearchHit[]>;
1442
+ /** Meet wave 3: name a recording (`null` = back to the meeting's title and date). */
1443
+ renameRecording(recordingId: string, title: string | null): Promise<void>;
1444
+ /** Meet wave 3: archive (soft delete) or restore a recording. Never a purge. */
1445
+ setRecordingArchived(recordingId: string, archived: boolean): Promise<void>;
1431
1446
  scheduleMeeting(input: ScheduleMeetingInput): Promise<MeetingRecord>;
1432
1447
  /** `expectedVersion` from the row you edited; a stale one is a `conflict`. */
1433
1448
  updateMeeting(meetingId: MeetingId, changes: MeetingChanges, options?: {
@@ -1521,6 +1536,50 @@ declare function settingsPatch(settings: MeetingSettings): Record<string, unknow
1521
1536
  */
1522
1537
  declare function changesPatch(changes: MeetingChanges): Record<string, unknown>;
1523
1538
  declare function createMeetRepository(options: RepositoryOptions): MeetRepository;
1539
+ /** One row of the recordings library (`meet_my_recordings`). */
1540
+ interface LibraryRecording {
1541
+ readonly id: string;
1542
+ readonly meetingId: MeetingId;
1543
+ readonly meetingTitle: string;
1544
+ readonly meetingSlug: string;
1545
+ /** The reader's strongest role on the meeting. */
1546
+ readonly role: "host" | "cohost" | "invitee" | "attendee";
1547
+ readonly state: RecordingState;
1548
+ /** A name a host gave it; null = show the meeting title. */
1549
+ readonly title: string | null;
1550
+ readonly fileId: string | null;
1551
+ readonly startedAt: string | null;
1552
+ readonly endedAt: string | null;
1553
+ readonly durationMs: number | null;
1554
+ readonly sizeBytes: number | null;
1555
+ readonly failureReason: string | null;
1556
+ readonly archivedAt: string | null;
1557
+ readonly organizationId: OrganizationId;
1558
+ }
1559
+ declare function projectLibraryRecording(row: Row): LibraryRecording;
1560
+ /** Where a search hit was found. */
1561
+ type MeetingSearchSource = "title" | "summary" | "decision" | "action_item" | "note" | "transcript";
1562
+ /** One hit from `meet_search`. `snippet` marks matched words with ⟦ ⟧. */
1563
+ interface MeetingSearchHit {
1564
+ readonly meetingId: MeetingId;
1565
+ readonly meetingTitle: string;
1566
+ readonly meetingSlug: string;
1567
+ readonly meetingAt: string | null;
1568
+ readonly source: MeetingSearchSource;
1569
+ /** The note or transcript segment id (the meeting id for a title hit). */
1570
+ readonly refId: string;
1571
+ readonly speaker: string | null;
1572
+ readonly snippet: string;
1573
+ /** When it was said / written. */
1574
+ readonly at: string | null;
1575
+ readonly rank: number;
1576
+ }
1577
+ declare function projectSearchHit(row: Row): MeetingSearchHit;
1578
+ /** A snippet split into plain and matched runs, for highlighting without HTML. */
1579
+ declare function snippetRuns(snippet: string): readonly {
1580
+ readonly text: string;
1581
+ readonly hit: boolean;
1582
+ }[];
1524
1583
 
1525
1584
  /**
1526
1585
  * THE CALL CENTER — ringing, answering, and the six ways a call ends badly.
@@ -2266,6 +2325,15 @@ interface MeetingSessionOptions {
2266
2325
  * to `engine.rejoin()`.
2267
2326
  */
2268
2327
  onAdmitted?: (() => void) | undefined;
2328
+ /**
2329
+ * 🚨 THE HOST DENIED *US*. Called once per denial verdict that names this
2330
+ * knocker. Without it the waiting room kept promising "you will join
2331
+ * automatically" to a person the host had already turned away (production
2332
+ * 2026-09-27). The provider wires it to `engine.rejoin()`: the server then
2333
+ * refuses the mint with its own sentence ("The host did not admit you…"),
2334
+ * so the words on screen are the server's decision, never the client's guess.
2335
+ */
2336
+ onDenied?: (() => void) | undefined;
2269
2337
  }
2270
2338
  interface MeetingSession {
2271
2339
  /** Subscribe to the room's out-of-band plane. Hosts see knocks; guests see verdicts. */
@@ -2357,39 +2425,6 @@ declare function createRecordingController(options: RecordingControllerOptions):
2357
2425
  /** The indicator's words. One definition, so no surface invents a softer one. */
2358
2426
  declare function recordingLabel(state: RecordingState): string | null;
2359
2427
 
2360
- /**
2361
- * MEET WAVE 1 — the two server calls a management screen makes (0.8).
2362
- *
2363
- * Every WRITE (schedule, edit, reschedule, cancel, archive, occurrence change,
2364
- * invite, answer) goes straight to the database through `MeetRepository` — the
2365
- * database decides who may. What a browser cannot do is send an email with a
2366
- * calendar object in it, so after any of those writes a screen calls
2367
- * `announceMeeting`: the server tells each invitee exactly what they have not
2368
- * been told (invitation, update or cancellation, each with its `.ics`), and
2369
- * re-queues reminders. It is idempotent — call it after every save.
2370
- */
2371
-
2372
- interface AnnounceResult {
2373
- readonly invitations: number;
2374
- readonly updates: number;
2375
- readonly cancellations: number;
2376
- readonly alreadyCurrent: number;
2377
- readonly remindersQueued: number;
2378
- readonly remindersCancelled: number;
2379
- }
2380
- /** Bring every invitee's inbox and calendar up to date. The host, a co-host or an editor. */
2381
- declare function announceMeeting(api: MeetApi, meetingId: MeetingId): Promise<AnnounceResult>;
2382
- /**
2383
- * Put a one-off meeting on the caller's own connected Google Calendar. GATED by
2384
- * the server to Google's review accounts while the Calendar scope is in review —
2385
- * a refusal is a `forbidden` MeetError carrying the server's sentence. Everyone
2386
- * already gets the meeting into any calendar through the invitation's `.ics`.
2387
- */
2388
- declare function pushMeetingToGoogleCalendar(api: MeetApi, meetingId: MeetingId, target: {
2389
- connectionId: string;
2390
- calendarId?: string;
2391
- }): Promise<Record<string, unknown>>;
2392
-
2393
2428
  /**
2394
2429
  * THE TOKEN FLOW — the ONLY authorization path into a room (R1).
2395
2430
  *
@@ -2546,6 +2581,12 @@ declare const MEET_ROUTES: {
2546
2581
  readonly meetingAnnounce: "/api/v1/meet/meetings/{meeting_id}/announce";
2547
2582
  /** Put a one-off meeting on the caller's own Google Calendar (gated by the server). */
2548
2583
  readonly meetingGoogleCalendar: "/api/v1/meet/meetings/{meeting_id}/google-calendar";
2584
+ /**
2585
+ * Meet wave 3: the recap email after the meeting. `GET` = the draft (subject,
2586
+ * body from the wrap-up, who it can go to); `POST` = send what the host
2587
+ * reviewed (`dry_run` renders and queues nothing). Never sent automatically.
2588
+ */
2589
+ readonly meetingFollowUp: "/api/v1/meet/meetings/{meeting_id}/follow-up";
2549
2590
  /**
2550
2591
  * Meet wave 2: EVERY in-meeting host power (mute, stop video, mute all, ask to
2551
2592
  * unmute, spotlight, lower hands, co-host, hand over, rename, remove, report).
@@ -2563,198 +2604,6 @@ declare const ROOM_TOKEN_HEADER = "x-meet-room-token";
2563
2604
  declare function parseRoomToken(body: unknown, roomName: RoomName): RoomToken;
2564
2605
  declare function createTokenClient(options: TokenClientOptions): TokenClient;
2565
2606
 
2566
- /**
2567
- * THE ROOM ENGINE — the object a host mounts once and never reasons about.
2568
- *
2569
- * It owns every sequence a calling product gets wrong: ask for a token, connect,
2570
- * project the roster, carry the in-room control plane, keep the phase honest
2571
- * through a reconnect, and tear everything down exactly once. A consumer's job
2572
- * is `engine.join(...)` and rendering a snapshot.
2573
- *
2574
- * THREE rules are structural here, not documented-and-hoped:
2575
- *
2576
- * 1. **The phase never lies.** `connecting` becomes `connected` only when the
2577
- * driver says so, and any failure becomes `failed` WITH the remedy attached
2578
- * to the snapshot. There is no path that leaves a spinner running forever —
2579
- * the single most common complaint about every video product.
2580
- * 2. **Role comes from the TOKEN, never from the client.** A participant's role
2581
- * is read out of the server-minted metadata. A client that could decide it
2582
- * was host could mute anyone; here the claim is unforgeable because the
2583
- * server signed it.
2584
- * 3. **Teardown is idempotent and total.** Leaving twice, or unmounting during
2585
- * a connect, disconnects once and leaves no listener behind. The half-torn
2586
- * room is what produces "my camera light stays on after the call".
2587
- */
2588
-
2589
- interface JoinRequest {
2590
- readonly roomName: RoomName;
2591
- readonly meetingId?: string | undefined;
2592
- readonly slug?: string | undefined;
2593
- /** `audio` joins with the camera off. Same room, same code path (D3). */
2594
- readonly mode: CallMode;
2595
- /**
2596
- * Whether to ATTEMPT the microphone on join. Defaults to true. `false` is a
2597
- * deliberate listen-only join — the pre-join screen's "Join with microphone"
2598
- * toggle. Either way the join itself never depends on the outcome; a refused
2599
- * device lands in `snapshot.media.microphone.unavailable`, not in a throw.
2600
- */
2601
- readonly microphone?: boolean | undefined;
2602
- }
2603
- interface RoomEngineOptions {
2604
- store: MeetStore;
2605
- tokens: TokenClient;
2606
- identity: MeetIdentity;
2607
- driverFactory: RoomDriverFactory;
2608
- /** Canonical public metadata read; room packets alone never authorize teardown. */
2609
- resolveMeeting?: ((slug: string) => Promise<MeetingRecord>) | undefined;
2610
- onDiagnostic?: ((event: {
2611
- level: "info" | "warn" | "error";
2612
- message: string;
2613
- remedy?: string;
2614
- }) => void) | undefined;
2615
- /** Fired when the local participant leaves for any reason, including kicked. */
2616
- onLeft?: ((reason: string | null) => void) | undefined;
2617
- now?: (() => number) | undefined;
2618
- /** Remembered noise suppression / background choice (Meet wave 2). */
2619
- effectsStorage?: EffectsStorage | undefined;
2620
- /**
2621
- * The device the person PICKED for each kind (the pre-join pickers, the
2622
- * settings panel). Applied before any track is created — through 0.7.x the
2623
- * pickers stored a choice nothing ever read, so every join used the browser
2624
- * default whatever the screen said.
2625
- */
2626
- resolveDevice?: ((kind: "audioinput" | "videoinput" | "audiooutput") => string | null) | undefined;
2627
- }
2628
- interface RoomEngine {
2629
- join(request: JoinRequest): Promise<void>;
2630
- /**
2631
- * 🚨 THE HOST SAID YES — COME IN.
2632
- *
2633
- * A lobby token is minted with `canPublish: false` and `canSubscribe: false`
2634
- * (that is what makes the lobby real rather than a screen over a live
2635
- * microphone), so admission is not a UI state change: the cached token has to
2636
- * be thrown away and a NEW one minted with the grants the server now gives.
2637
- * Without this the waiting room's own sentence — "you will join automatically
2638
- * once you are admitted" — is a lie, and the guest waits forever.
2639
- */
2640
- rejoin(): Promise<void>;
2641
- leave(): Promise<void>;
2642
- setMicrophoneEnabled(enabled: boolean): Promise<void>;
2643
- setCameraEnabled(enabled: boolean): Promise<void>;
2644
- setScreenShareEnabled(enabled: boolean): Promise<void>;
2645
- raiseHand(raised: boolean): Promise<void>;
2646
- react(emoji: string): Promise<void>;
2647
- /**
2648
- * A PRIVATE message to one person (Meet wave 2). Data plane only, addressed
2649
- * to that identity, NEVER saved — see `core/chat.ts` for the saved,
2650
- * everyone-chat, which goes through the server.
2651
- */
2652
- sendPrivateChat(to: ParticipantIdentity, text: string): Promise<void>;
2653
- /**
2654
- * Change this participant's own media processing — noise suppression and the
2655
- * camera background — on the live tracks, and remember it in this browser.
2656
- * A refused effect is a diagnostic and a revert, never a lost camera.
2657
- */
2658
- setEffects(next: Partial<MediaEffects>): Promise<void>;
2659
- /** Whether this browser can run a background effect at all (absent, never dead). */
2660
- supportsVideoEffects(): boolean;
2661
- /** Choose the speaker (audio output) for this meeting. */
2662
- switchDevice(kind: "audioinput" | "videoinput" | "audiooutput", deviceId: string): Promise<void>;
2663
- pin(identity: ParticipantIdentity | null): void;
2664
- setLayout(layout: StageLayout): void;
2665
- attach(identity: ParticipantIdentity, kind: "camera" | "microphone" | "screen", element: HTMLMediaElement): () => void;
2666
- token(): RoomToken | null;
2667
- dispose(): Promise<void>;
2668
- }
2669
- /** Where this browser keeps a participant's media choices. */
2670
- interface EffectsStorage {
2671
- load(): MediaEffects;
2672
- save(effects: MediaEffects): void;
2673
- }
2674
- /**
2675
- * 🚨 THE SERVER'S WORD, ON TWO CARRIERS (MRI-A11, feedback `7418ef78`).
2676
- *
2677
- * aidream mints the role into the token's participant METADATA (a JSON blob)
2678
- * AND as a LiveKit participant ATTRIBUTE. Both come out of one decision in
2679
- * `services/meet/service.py`; neither can be written by a client. The attribute
2680
- * is preferred because LiveKit delivers the attribute map on every roster
2681
- * update, while a roster event can carry an empty metadata string — which is
2682
- * precisely how, through 2026-09-08, EVERY participant projected as
2683
- * "participant" and no host ever saw a host control in a browser.
2684
- *
2685
- * These strings are pinned on the server side too: `test_meet_service.py` reads
2686
- * them out of THIS file and fails if the two sides drift.
2687
- */
2688
- declare const PARTICIPANT_ROLE_ATTRIBUTE = "matrx.participant.role";
2689
- declare const PARTICIPANT_CAN_RECORD_ATTRIBUTE = "matrx.participant.can_record";
2690
- /**
2691
- * Metadata is SERVER-MINTED and therefore trusted; it is still parsed
2692
- * defensively, because a malformed blob must degrade a tile to "participant"
2693
- * rather than throw inside a roster event and freeze the whole grid.
2694
- */
2695
- declare function projectParticipant(driver: DriverParticipant, joinedAt: string): MeetParticipant;
2696
- declare function createRoomEngine(options: RoomEngineOptions): RoomEngine;
2697
- /** Exported for the host-power check in UI code — one definition, one rank. */
2698
- declare function canModerate(participant: MeetParticipant | undefined): boolean;
2699
- declare function hostUserIds(participants: readonly MeetParticipant[]): readonly UserId[];
2700
-
2701
- /**
2702
- * A PARTICIPANT'S OWN MEDIA CHOICES, REMEMBERED (Meet wave 2).
2703
- *
2704
- * Noise suppression and the camera background are personal and per device —
2705
- * Zoom and Meet both remember them — so they live in this browser, never on a
2706
- * server. Blocked storage means the defaults (suppression on, no background),
2707
- * never a failed join.
2708
- */
2709
-
2710
- declare function createWebEffectsStorage(key?: string): EffectsStorage;
2711
-
2712
- /**
2713
- * THE IN-ROOM SERVER CALLS (Meet wave 2) — host moderation and the SAVED chat.
2714
- *
2715
- * 🚨 **Why these are server calls and not data packets.** Through 0.7.x a host's
2716
- * "Mute" and "Remove" were published on the room's data plane (`mx.host`) and
2717
- * nothing anywhere listened: the buttons did nothing. A client cannot be the
2718
- * authority on host power, and a guest cannot write the database. So both go to
2719
- * aidream (`POST /v1/meet/moderate`, `POST|GET /v1/meet/chat`), which decides the
2720
- * actor's role from the meeting itself, performs the act through LiveKit's server
2721
- * API, writes what is durable, and tells the room on its own `matrx.meet.*` topic.
2722
- *
2723
- * A guest proves who they are with the room pass this server signed for them, in
2724
- * `x-meet-room-token` — a header, never a URL.
2725
- */
2726
-
2727
- /** Every host power the server performs. One vocabulary with `services/meet/moderation.py`. */
2728
- type ModerationAction = "mute" | "stop_video" | "stop_screen" | "mute_all" | "ask_unmute" | "ask_all_unmute" | "spotlight" | "lower_hand" | "lower_all_hands" | "make_cohost" | "revoke_cohost" | "transfer_host" | "rename" | "remove" | "report";
2729
- declare const MODERATION_ACTIONS: readonly ModerationAction[];
2730
- interface ModerationRequest {
2731
- readonly action: ModerationAction;
2732
- readonly identity?: ParticipantIdentity | null | undefined;
2733
- readonly name?: string | undefined;
2734
- readonly reason?: string | undefined;
2735
- }
2736
- interface ModerationResult {
2737
- readonly action: ModerationAction;
2738
- readonly affected: readonly string[];
2739
- /** The server's sentence for the actor. */
2740
- readonly message: string;
2741
- }
2742
- interface InRoomClient {
2743
- moderate(request: ModerationRequest): Promise<ModerationResult>;
2744
- /** Send a message to EVERYONE. Saved; shows at once as pending, then as saved. */
2745
- sendChat(text: string): Promise<void>;
2746
- /** Read the saved chat once per join, so a late joiner sees what was said. */
2747
- loadChat(): Promise<void>;
2748
- }
2749
- interface InRoomClientOptions {
2750
- api: MeetApi;
2751
- store: MeetStore;
2752
- /** The room pass for THIS meeting, or null (a signed-in member needs none). */
2753
- resolveRoomToken: () => string | null;
2754
- now?: (() => number) | undefined;
2755
- }
2756
- declare function createInRoomClient(options: InRoomClientOptions): InRoomClient;
2757
-
2758
2607
  /**
2759
2608
  * THE MEETING RECORD — what a meeting IS once the room is gone (MRI-D2).
2760
2609
  *
@@ -2791,6 +2640,12 @@ interface MeetingRecordRestriction {
2791
2640
  readonly message: string;
2792
2641
  readonly remedy: string;
2793
2642
  }
2643
+ /** One visible access state, made from restrictions that share one remedy. */
2644
+ interface MeetingRecordRestrictionNotice {
2645
+ readonly parts: readonly MeetingRecordRestriction["part"][];
2646
+ readonly message: string;
2647
+ readonly remedy: string;
2648
+ }
2794
2649
  /** A run of consecutive lines by one speaker — how a person reads a transcript. */
2795
2650
  interface TranscriptBlock {
2796
2651
  readonly id: string;
@@ -2824,6 +2679,12 @@ interface MeetingRecordBundle {
2824
2679
  */
2825
2680
  readonly names: NameBook;
2826
2681
  }
2682
+ /**
2683
+ * The current wrap-up summary. Repairs preserve the prior durable row for
2684
+ * history, then mark it `superseded_by`; every current-facing projection must
2685
+ * therefore make the same distinction as decisions and action items.
2686
+ */
2687
+ declare function activeWrapUpSummary(notes: readonly MeetingNote[]): MeetingNote | null;
2827
2688
  /**
2828
2689
  * Load the record parts the reader may request; state all restrictions.
2829
2690
  *
@@ -2856,13 +2717,56 @@ declare function wasRefused(bundle: MeetingRecordBundle, part: MeetingRecordRest
2856
2717
  */
2857
2718
  declare function absenceSentence(bundle: MeetingRecordBundle, part: MeetingRecordRestriction["part"], absence: string): string;
2858
2719
  /**
2859
- * GROUP THE TRANSCRIPT THE WAY A PERSON READS IT: consecutive lines by one
2860
- * speaker become one block with one timestamp. Reading thirty-two rows each
2861
- * re-stamped with a name and a clock is how a transcript becomes unreadable.
2720
+ * 🚨 **"NO WRAP-UP WAS WRITTEN" IS A CLAIM, AND FOR ~33 SECONDS IT WAS FALSE.**
2721
+ *
2722
+ * On production 2026-09-09 the HOST's record view said *"No wrap-up was written
2723
+ * for this meeting"* between 04:46:42 — the flip from the room to the record —
2724
+ * and 04:47:15, when the wrap-up landed. The read was permitted and genuinely
2725
+ * empty, so this is not the refusal class above; it is the OTHER way a screen
2726
+ * lies, by reading "not here yet" as "never happened". A person who reads *"no
2727
+ * wrap-up was written"* closes the tab.
2728
+ *
2729
+ * The meeting row already knows the difference, and it is not a guess: the
2730
+ * wrap-up is a durable job, `enqueue_wrap_up` stamps
2731
+ * `metadata.note_taker.intelligence.wrap_up` when it queues it, and the graph
2732
+ * action stamps `state: "failed"` when the pass refuses TERMINALLY. So:
2733
+ *
2734
+ * - a summary exists → `written`;
2735
+ * - the notes read was refused → `refused` (never an absence claim);
2736
+ * - the job is marked failed → `failed`, in the server's own words;
2737
+ * - the job is queued or running → `in-progress`, with a refresh affordance;
2738
+ * - nothing ever queued one → `absent`, which is the only EARNED absence.
2739
+ *
2740
+ * There is no timeout that flips `in-progress` into `absent`. A wrap-up that is
2741
+ * genuinely stuck says it is still being written — which is TRUE — rather than
2742
+ * inventing a verdict this browser has no way to reach.
2862
2743
  */
2863
- declare function groupTranscript(segments: readonly TranscriptSegment[], names?: NameBook): readonly TranscriptBlock[];
2864
- /**
2865
- * WHAT THE RECORDING IS DOING, in a sentence — including the two states people
2744
+ type WrapUpStatus = {
2745
+ readonly state: "written";
2746
+ } | {
2747
+ readonly state: "in-progress";
2748
+ readonly message: string;
2749
+ readonly canRefresh: true;
2750
+ } | {
2751
+ readonly state: "failed";
2752
+ readonly message: string;
2753
+ } | {
2754
+ readonly state: "refused";
2755
+ readonly message: string;
2756
+ } | {
2757
+ readonly state: "absent";
2758
+ readonly message: string;
2759
+ };
2760
+ /** Where the wrap-up actually is. Never "absent" over a job that is running. */
2761
+ declare function wrapUpStatus(bundle: MeetingRecordBundle): WrapUpStatus;
2762
+ /**
2763
+ * GROUP THE TRANSCRIPT THE WAY A PERSON READS IT: consecutive lines by one
2764
+ * speaker become one block with one timestamp. Reading thirty-two rows each
2765
+ * re-stamped with a name and a clock is how a transcript becomes unreadable.
2766
+ */
2767
+ declare function groupTranscript(segments: readonly TranscriptSegment[], names?: NameBook): readonly TranscriptBlock[];
2768
+ /**
2769
+ * WHAT THE RECORDING IS DOING, in a sentence — including the two states people
2866
2770
  * hit most and understand least. `processing` is not "broken" and `failed` is
2867
2771
  * never silence: both say what happened and what to do.
2868
2772
  */
@@ -2913,6 +2817,14 @@ interface GuestRecordAccessResolution {
2913
2817
  /** `meeting` when a host chose for this one; `organization` otherwise. */
2914
2818
  readonly source: "meeting" | "organization";
2915
2819
  }
2820
+ /**
2821
+ * Repeated policy refusals are one access state, not four alarming cards.
2822
+ *
2823
+ * The individual rows remain durable data for absence handling in their own
2824
+ * sections. This projection groups only rows with the exact same remedy, so a
2825
+ * distinct transport or integrity failure remains visible on its own.
2826
+ */
2827
+ declare function summarizeMeetingRecordRestrictions(restrictions: readonly MeetingRecordRestriction[]): readonly MeetingRecordRestrictionNotice[];
2916
2828
  /**
2917
2829
  * Read the record the server shares with THIS guest.
2918
2830
  *
@@ -2949,4 +2861,398 @@ declare function setGuestRecordAccess(args: {
2949
2861
  /** What a guest is told when the level shares nothing. Rendered, never hidden. */
2950
2862
  declare const GUEST_RECORD_NOT_SHARED: Readonly<Record<"recording" | "transcript" | "notes" | "attendance", string>>;
2951
2863
 
2952
- export { type AccessTokenSource, type AdmissionState, type AnnounceResult, type ApplyRoomEventArgs, type AskArgs, type AudioProcessing, CALL_INVITE_KIND, CALL_INVITE_VERSION, CONSENT_NOTICE_ATTRIBUTE, type CallCenter, type CallCenterOptions, type CallId, type CallInvite, type CallInviteHandlerOptions, type CallInvitePayload, type CallMode, type CallState, type CallablePerson, type ConnectionQuality, 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, FILE_SESSION_PATH, 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_TRACK, type InRoomClient, type InRoomClientOptions, type InviteeInput, type InviteeRole, type JoinRequest, type JsonObject, type JsonValue, LISTEN_ONLY_NOTICE, type LiveKitDriverOptions, type LobbyEntry, type LocalMediaState, type LocalTrackState, MEETING_INVITE_KIND, MEETING_INVITE_VERSION, MEET_ANSWER_CONTEXT_EVENT, MEET_ANSWER_EVENTS, MEET_EVENTS, MEET_ROUTES, MEET_SCHEMA, MODERATION_ACTIONS, type MediaDevicesLike, type MediaEffects, type MediaUnavailable, type MeetAi, type MeetAiAnswer, type MeetAiOptions, type MeetAnswerCoverage, type MeetApi, type MeetApiOptions, type MeetApiRequest, type MeetDevice, MeetError, type MeetErrorCode, type MeetEvent, type MeetFileMedia, type MeetGrants, type MeetIdentity, type MeetParticipant, type MeetRepository, type MeetRoomDriver, type MeetSnapshot, type MeetStore, type MeetingAttendee, type MeetingChanges, type MeetingId, type MeetingInviteHandlerOptions, type MeetingInvitePayload, type MeetingInvitee, type MeetingKind, type MeetingNote, type MeetingNoteKind, type MeetingOccurrence, type MeetingRecord, type MeetingRecordBundle, type MeetingRecordRestriction, type MeetingSession, type MeetingSessionOptions, type MeetingSettings, type ModerationAction, type ModerationNotice, type ModerationRequest, type ModerationResult, NOTE_TAKER_IDENTITY_PREFIX, NOTE_TAKER_METADATA_KEY, NO_GRANTS, type NameBook, type NoteTakerDispatch, type NoteTakerRefusal, type NoteTakerState, type NoteTakerStatus, ORGANIZATION_HEADER, type OccurrenceOverride, type OccurrenceState, type OrganizationId, PARTICIPANT_CAN_RECORD_ATTRIBUTE, PARTICIPANT_ROLE_ATTRIBUTE, type ParticipantIdentity, type ParticipantRole, REACTION_TTL_MS, REFUSED_ABSENCE, ROLE_RANK, ROOM_EVENTS, ROOM_EVENT_PREFIX, ROOM_TOKEN_HEADER, RPCS, type RecordingController, type RecordingControllerOptions, type RecordingPolicy, type RecordingRecord, type RecordingState, type RepositoryOptions, type RoomChatMessage, type RoomDriverFactory, type RoomDriverHandlers, type RoomEngine, type RoomEngineOptions, type RoomEventOutcome, type RoomEventTopic, type RoomName, type RoomPhase, type RoomReaction, type RoomToken, type RoomTokenStorage, type RsvpAnswer, type RsvpInvitation, type RsvpState, type ScheduleMeetingInput, type Stage, type StageArgs, type StageLayout, TABLES, TRANSCRIPTION_ATTRIBUTES, TRANSCRIPTION_TOPIC, type TokenClient, type TokenClientOptions, type TokenRequest, type TranscriptBlock, type TranscriptSegment, type TranscriptSource, type UpcomingOccurrence, type UserId, type VideoEffect, type WrapUpAnnouncement, absenceSentence, announceMeeting, applyRoomEvent, asCallId, asMeetingId, asOrganizationId, asParticipantIdentity, asRoomName, asUserId, callInviteAction, callInviteText, callStateLabel, canModerate, canModerateParticipant, canTransition, captionSegment, changesPatch, computeStage, countLabel, createCallCenter, createCallInviteHandler, createDeviceManager, createInRoomClient, createLiveKitDriver, createMeetAi, createMeetApi, createMeetRepository, createMeetStore, createMeetingInviteHandler, createMeetingSession, createMemoryDeviceStorage, createRecordingController, createRoomEngine, createTokenClient, createWebDeviceStorage, createWebEffectsStorage, createWebRoomTokenStorage, displayNameFor, fileDownloadPath, gridColumns, groupTranscript, guestRecordAccessExplanation, guestRecordAccessLabel, guestRecordAccessSetting, hasEnded, hostUserIds, invalidResponse, isGuest, isGuestIdentity, isHostRole, isListenOnly, isNoteTaker, isRealName, isRoomEventTopic, learnName, learnNames, listenOnlyNotice, loadGuestMeetingRecord, loadMeetingRecord, mapConnectionState, mapQuality, meetingInviteAction, meetingLink, meetingPath, meetingSlug, meetingTopic, meetingWhen, mergeCaption, mergeRecord, normalizeDeviceError, normalizeMeetError, normalizeSpoken, noteTakerIdentity, noteTakerLabel, noteTakerParticipant, noteTakerStatus, orderParticipants, parseRoomToken, participantSummary, playableRecordingFileId, projectInvitee, projectNote, projectOccurrence, projectOverride, projectParticipant, projectRsvpInvitation, projectSegment, projectUpcoming, pushMeetingToGoogleCalendar, readServerRefusal, readableTranscript, recordingLabel, recordingNotice, roleOf, roomTopic, setGuestRecordAccess, settingsPatch, snapshotParticipant, unavailableFrom, userTopic, wasRefused };
2864
+ /**
2865
+ * AFTER THE MEETING (Meet wave 3) — the pure pieces every client needs once the
2866
+ * room is gone: the attendance report, the transcript and notes as documents,
2867
+ * a moment in the transcript as a moment in the recording, search within one
2868
+ * transcript, and the two server calls behind the recap email and the saved
2869
+ * chat.
2870
+ *
2871
+ * Nothing here computes MEANING (same rule as `record.ts`): the summary,
2872
+ * decisions and action items are the wrap-up's durable rows; this file only
2873
+ * arranges, measures and formats them. Every function is framework-free so a
2874
+ * worker, a test and a browser produce byte-identical exports.
2875
+ */
2876
+
2877
+ /**
2878
+ * Who someone was, in the words an attendance report uses. `signed-in guest` is
2879
+ * an account from outside the meeting's organization that followed the link;
2880
+ * `guest` has no account at all.
2881
+ */
2882
+ type AttendanceKind = "host" | "cohost" | "member" | "signed-in guest" | "guest" | "assistant";
2883
+ interface AttendanceRow {
2884
+ readonly id: string;
2885
+ readonly identity: string;
2886
+ readonly name: string;
2887
+ readonly userId: string | null;
2888
+ readonly kind: AttendanceKind;
2889
+ /** First time they were in the room; null when they knocked and never came in. */
2890
+ readonly joinedAt: string | null;
2891
+ /** When they left; null while still in the room or when the leave was never recorded. */
2892
+ readonly leftAt: string | null;
2893
+ /**
2894
+ * Time in the room. A missing `leftAt` is measured to the meeting's end —
2895
+ * the room closed around them — and `null` only when neither is known.
2896
+ */
2897
+ readonly durationMs: number | null;
2898
+ /** True when `durationMs` was measured to the meeting's end, not a recorded leave. */
2899
+ readonly durationToEnd: boolean;
2900
+ readonly admission: MeetingAttendee["admissionState"];
2901
+ }
2902
+ /**
2903
+ * The attendance report of ONE meeting, host first then by arrival. People who
2904
+ * knocked and were never let in are kept (with no join time) — "who asked to
2905
+ * come in" is part of the record — but a denied knock has no duration.
2906
+ */
2907
+ declare function attendanceReport(meeting: MeetingRecord, attendees: readonly MeetingAttendee[], names?: NameBook): readonly AttendanceRow[];
2908
+ /** Headline numbers for the top of a report. The assistant is never a person. */
2909
+ declare function attendanceTotals(rows: readonly AttendanceRow[]): {
2910
+ readonly people: number;
2911
+ readonly guests: number;
2912
+ readonly neverAdmitted: number;
2913
+ readonly averageMs: number | null;
2914
+ };
2915
+ declare const ATTENDANCE_CSV_HEADER: readonly ["Meeting", "Meeting started", "Name", "Attended as", "Joined", "Left", "Minutes in the meeting", "Admission"];
2916
+ /**
2917
+ * CSV for a spreadsheet: one header row, one row per person, ISO timestamps (a
2918
+ * spreadsheet parses them; a formatted local clock loses the zone). Several
2919
+ * meetings — a series — concatenate with one header.
2920
+ */
2921
+ declare function attendanceCsv(reports: readonly {
2922
+ meeting: MeetingRecord;
2923
+ rows: readonly AttendanceRow[];
2924
+ }[]): string;
2925
+ /**
2926
+ * Where an instant of the meeting falls in the RECORDING, in milliseconds from
2927
+ * its start — or `null` when the recording does not cover it (a recording is
2928
+ * often a few minutes of a longer meeting). Never a clamp: seeking a line that
2929
+ * was said before recording began to 0:00 would play the wrong words.
2930
+ */
2931
+ declare function recordingOffsetMs(at: string, recording: RecordingRecord | null): number | null;
2932
+ /** `m:ss` or `h:mm:ss` — a position in a recording or a meeting. */
2933
+ declare function clockPosition(offsetMs: number): string;
2934
+ interface TranscriptMatch {
2935
+ readonly segmentId: string;
2936
+ /** [start, end) character ranges inside the segment's text, in order. */
2937
+ readonly ranges: readonly (readonly [number, number])[];
2938
+ }
2939
+ /**
2940
+ * Case-insensitive search of the transcript text AND the speaker's
2941
+ * name. Returns matches in transcript order. An empty query matches nothing.
2942
+ */
2943
+ declare function searchTranscript(segments: readonly TranscriptSegment[], query: string, names?: NameBook): readonly TranscriptMatch[];
2944
+ type TranscriptFormat = "txt" | "md" | "vtt";
2945
+ interface ExportOptions {
2946
+ readonly names?: NameBook;
2947
+ /** Timestamps as a clock of day (default) or offsets from `zero`. */
2948
+ readonly locale?: string;
2949
+ readonly timeZone?: string;
2950
+ }
2951
+ /**
2952
+ * The transcript as a document. `txt` and `md` group consecutive lines by one
2953
+ * speaker with the clock of day; `vtt` is WebVTT cued from the recording's
2954
+ * start when a recording exists (so it plays as captions over it), otherwise
2955
+ * from the meeting's start — and a cue ends where the next line begins.
2956
+ */
2957
+ declare function transcriptDocument(bundle: Pick<MeetingRecordBundle, "meeting" | "transcript" | "recording" | "names">, format: TranscriptFormat, options?: ExportOptions): string;
2958
+ /**
2959
+ * The meeting's notes as Markdown: when, who attended, the summary, decisions
2960
+ * and action items (with owners), and the link back. What a person pastes into
2961
+ * a doc or an email; also the source for PDF / Word export.
2962
+ */
2963
+ declare function notesDocument(bundle: Pick<MeetingRecordBundle, "meeting" | "summary" | "decisions" | "actionItems" | "attendees" | "names">, options?: {
2964
+ readonly link?: string | null;
2965
+ readonly when?: string | null;
2966
+ }): string;
2967
+ /** How long the meeting ran, in words ("42 minutes"), or null when unknown. */
2968
+ declare function meetingLength(meeting: MeetingRecord): string | null;
2969
+ interface MeetingChatHistory {
2970
+ readonly messages: readonly RoomChatMessage[];
2971
+ /** The server's refusal, in its words, when the history could not be read. */
2972
+ readonly refusal: {
2973
+ readonly message: string;
2974
+ readonly remedy: string;
2975
+ } | null;
2976
+ }
2977
+ /**
2978
+ * The saved chat of a finished meeting (`GET /v1/meet/chat`). Private messages
2979
+ * are never saved, by design, so they are never here. A refusal is returned as
2980
+ * data — the record still renders everything else.
2981
+ */
2982
+ declare function loadMeetingChat(api: MeetApi, meetingId: MeetingId, roomToken?: string | null): Promise<MeetingChatHistory>;
2983
+ /** One person the recap can go to, as the server resolved them. */
2984
+ interface FollowUpRecipient {
2985
+ /** Stable key for the choice: `user:<id>` or `email:<address>`. */
2986
+ readonly key: string;
2987
+ readonly name: string;
2988
+ /** The address, when this reader may see it (the host sees their guests'). */
2989
+ readonly email: string | null;
2990
+ readonly userId: string | null;
2991
+ /** `invitee`, `attendee` or both — where the server found them. */
2992
+ readonly sources: readonly ("invitee" | "attendee")[];
2993
+ /** False when the server has no way to reach them (no account, no address). */
2994
+ readonly reachable: boolean;
2995
+ }
2996
+ /** The draft the host reviews. The text is the wrap-up's own, arranged. */
2997
+ interface FollowUpDraft {
2998
+ readonly subject: string;
2999
+ readonly body: string;
3000
+ readonly recipients: readonly FollowUpRecipient[];
3001
+ /** False while the wrap-up has not been written — the draft says so. */
3002
+ readonly wrapUpWritten: boolean;
3003
+ /** When a recap for this meeting was last sent, and to how many. */
3004
+ readonly lastSent: {
3005
+ readonly at: string;
3006
+ readonly count: number;
3007
+ } | null;
3008
+ }
3009
+ interface FollowUpSendResult {
3010
+ readonly dryRun: boolean;
3011
+ /** Recipients the send queued (or, on a dry run, would queue). */
3012
+ readonly queued: readonly string[];
3013
+ /** Recipient keys skipped, each with the reason. */
3014
+ readonly skipped: readonly {
3015
+ readonly key: string;
3016
+ readonly reason: string;
3017
+ }[];
3018
+ }
3019
+ /** The recap draft for a finished meeting. The host, a co-host or an editor. */
3020
+ declare function loadFollowUpDraft(api: MeetApi, meetingId: MeetingId): Promise<FollowUpDraft>;
3021
+ /**
3022
+ * Send the recap the host reviewed. `dryRun` resolves every recipient and
3023
+ * renders every email but queues nothing — how a test proves the path without
3024
+ * mailing a real inbox. Never called automatically.
3025
+ */
3026
+ declare function sendFollowUp(api: MeetApi, meetingId: MeetingId, input: {
3027
+ readonly subject: string;
3028
+ readonly body: string;
3029
+ readonly recipientKeys: readonly string[];
3030
+ readonly dryRun?: boolean;
3031
+ }): Promise<FollowUpSendResult>;
3032
+
3033
+ /**
3034
+ * MEET WAVE 1 — the two server calls a management screen makes (0.8).
3035
+ *
3036
+ * Every WRITE (schedule, edit, reschedule, cancel, archive, occurrence change,
3037
+ * invite, answer) goes straight to the database through `MeetRepository` — the
3038
+ * database decides who may. What a browser cannot do is send an email with a
3039
+ * calendar object in it, so after any of those writes a screen calls
3040
+ * `announceMeeting`: the server tells each invitee exactly what they have not
3041
+ * been told (invitation, update or cancellation, each with its `.ics`), and
3042
+ * re-queues reminders. It is idempotent — call it after every save.
3043
+ */
3044
+
3045
+ interface AnnounceResult {
3046
+ readonly invitations: number;
3047
+ readonly updates: number;
3048
+ readonly cancellations: number;
3049
+ readonly alreadyCurrent: number;
3050
+ readonly remindersQueued: number;
3051
+ readonly remindersCancelled: number;
3052
+ }
3053
+ /** Bring every invitee's inbox and calendar up to date. The host, a co-host or an editor. */
3054
+ declare function announceMeeting(api: MeetApi, meetingId: MeetingId): Promise<AnnounceResult>;
3055
+ /**
3056
+ * Put a one-off meeting on the caller's own connected Google Calendar. GATED by
3057
+ * the server to Google's review accounts while the Calendar scope is in review —
3058
+ * a refusal is a `forbidden` MeetError carrying the server's sentence. Everyone
3059
+ * already gets the meeting into any calendar through the invitation's `.ics`.
3060
+ */
3061
+ declare function pushMeetingToGoogleCalendar(api: MeetApi, meetingId: MeetingId, target: {
3062
+ connectionId: string;
3063
+ calendarId?: string;
3064
+ }): Promise<Record<string, unknown>>;
3065
+
3066
+ /**
3067
+ * THE ROOM ENGINE — the object a host mounts once and never reasons about.
3068
+ *
3069
+ * It owns every sequence a calling product gets wrong: ask for a token, connect,
3070
+ * project the roster, carry the in-room control plane, keep the phase honest
3071
+ * through a reconnect, and tear everything down exactly once. A consumer's job
3072
+ * is `engine.join(...)` and rendering a snapshot.
3073
+ *
3074
+ * THREE rules are structural here, not documented-and-hoped:
3075
+ *
3076
+ * 1. **The phase never lies.** `connecting` becomes `connected` only when the
3077
+ * driver says so, and any failure becomes `failed` WITH the remedy attached
3078
+ * to the snapshot. There is no path that leaves a spinner running forever —
3079
+ * the single most common complaint about every video product.
3080
+ * 2. **Role comes from the TOKEN, never from the client.** A participant's role
3081
+ * is read out of the server-minted metadata. A client that could decide it
3082
+ * was host could mute anyone; here the claim is unforgeable because the
3083
+ * server signed it.
3084
+ * 3. **Teardown is idempotent and total.** Leaving twice, or unmounting during
3085
+ * a connect, disconnects once and leaves no listener behind. The half-torn
3086
+ * room is what produces "my camera light stays on after the call".
3087
+ */
3088
+
3089
+ interface JoinRequest {
3090
+ readonly roomName: RoomName;
3091
+ readonly meetingId?: string | undefined;
3092
+ readonly slug?: string | undefined;
3093
+ /** `audio` joins with the camera off. Same room, same code path (D3). */
3094
+ readonly mode: CallMode;
3095
+ /**
3096
+ * Whether to ATTEMPT the microphone on join. Defaults to true. `false` is a
3097
+ * deliberate listen-only join — the pre-join screen's "Join with microphone"
3098
+ * toggle. Either way the join itself never depends on the outcome; a refused
3099
+ * device lands in `snapshot.media.microphone.unavailable`, not in a throw.
3100
+ */
3101
+ readonly microphone?: boolean | undefined;
3102
+ }
3103
+ interface RoomEngineOptions {
3104
+ store: MeetStore;
3105
+ tokens: TokenClient;
3106
+ identity: MeetIdentity;
3107
+ driverFactory: RoomDriverFactory;
3108
+ /** Canonical public metadata read; room packets alone never authorize teardown. */
3109
+ resolveMeeting?: ((slug: string) => Promise<MeetingRecord>) | undefined;
3110
+ onDiagnostic?: ((event: {
3111
+ level: "info" | "warn" | "error";
3112
+ message: string;
3113
+ remedy?: string;
3114
+ }) => void) | undefined;
3115
+ /** Fired when the local participant leaves for any reason, including kicked. */
3116
+ onLeft?: ((reason: string | null) => void) | undefined;
3117
+ now?: (() => number) | undefined;
3118
+ /** Remembered noise suppression / background choice (Meet wave 2). */
3119
+ effectsStorage?: EffectsStorage | undefined;
3120
+ /**
3121
+ * The device the person PICKED for each kind (the pre-join pickers, the
3122
+ * settings panel). Applied before any track is created — through 0.7.x the
3123
+ * pickers stored a choice nothing ever read, so every join used the browser
3124
+ * default whatever the screen said.
3125
+ */
3126
+ resolveDevice?: ((kind: "audioinput" | "videoinput" | "audiooutput") => string | null) | undefined;
3127
+ }
3128
+ interface RoomEngine {
3129
+ join(request: JoinRequest): Promise<void>;
3130
+ /**
3131
+ * 🚨 THE HOST SAID YES — COME IN.
3132
+ *
3133
+ * A lobby token is minted with `canPublish: false` and `canSubscribe: false`
3134
+ * (that is what makes the lobby real rather than a screen over a live
3135
+ * microphone), so admission is not a UI state change: the cached token has to
3136
+ * be thrown away and a NEW one minted with the grants the server now gives.
3137
+ * Without this the waiting room's own sentence — "you will join automatically
3138
+ * once you are admitted" — is a lie, and the guest waits forever.
3139
+ */
3140
+ rejoin(): Promise<void>;
3141
+ leave(): Promise<void>;
3142
+ setMicrophoneEnabled(enabled: boolean): Promise<void>;
3143
+ setCameraEnabled(enabled: boolean): Promise<void>;
3144
+ setScreenShareEnabled(enabled: boolean): Promise<void>;
3145
+ raiseHand(raised: boolean): Promise<void>;
3146
+ react(emoji: string): Promise<void>;
3147
+ /**
3148
+ * A PRIVATE message to one person (Meet wave 2). Data plane only, addressed
3149
+ * to that identity, NEVER saved — see `core/chat.ts` for the saved,
3150
+ * everyone-chat, which goes through the server.
3151
+ */
3152
+ sendPrivateChat(to: ParticipantIdentity, text: string): Promise<void>;
3153
+ /**
3154
+ * Change this participant's own media processing — noise suppression and the
3155
+ * camera background — on the live tracks, and remember it in this browser.
3156
+ * A refused effect is a diagnostic and a revert, never a lost camera.
3157
+ */
3158
+ setEffects(next: Partial<MediaEffects>): Promise<void>;
3159
+ /** Whether this browser can run a background effect at all (absent, never dead). */
3160
+ supportsVideoEffects(): boolean;
3161
+ /** Choose the speaker (audio output) for this meeting. */
3162
+ switchDevice(kind: "audioinput" | "videoinput" | "audiooutput", deviceId: string): Promise<void>;
3163
+ pin(identity: ParticipantIdentity | null): void;
3164
+ setLayout(layout: StageLayout): void;
3165
+ attach(identity: ParticipantIdentity, kind: "camera" | "microphone" | "screen", element: HTMLMediaElement): () => void;
3166
+ token(): RoomToken | null;
3167
+ dispose(): Promise<void>;
3168
+ }
3169
+ /** Where this browser keeps a participant's media choices. */
3170
+ interface EffectsStorage {
3171
+ load(): MediaEffects;
3172
+ save(effects: MediaEffects): void;
3173
+ }
3174
+ /**
3175
+ * 🚨 THE SERVER'S WORD, ON TWO CARRIERS (MRI-A11, feedback `7418ef78`).
3176
+ *
3177
+ * aidream mints the role into the token's participant METADATA (a JSON blob)
3178
+ * AND as a LiveKit participant ATTRIBUTE. Both come out of one decision in
3179
+ * `services/meet/service.py`; neither can be written by a client. The attribute
3180
+ * is preferred because LiveKit delivers the attribute map on every roster
3181
+ * update, while a roster event can carry an empty metadata string — which is
3182
+ * precisely how, through 2026-09-08, EVERY participant projected as
3183
+ * "participant" and no host ever saw a host control in a browser.
3184
+ *
3185
+ * These strings are pinned on the server side too: `test_meet_service.py` reads
3186
+ * them out of THIS file and fails if the two sides drift.
3187
+ */
3188
+ declare const PARTICIPANT_ROLE_ATTRIBUTE = "matrx.participant.role";
3189
+ declare const PARTICIPANT_CAN_RECORD_ATTRIBUTE = "matrx.participant.can_record";
3190
+ /**
3191
+ * Metadata is SERVER-MINTED and therefore trusted; it is still parsed
3192
+ * defensively, because a malformed blob must degrade a tile to "participant"
3193
+ * rather than throw inside a roster event and freeze the whole grid.
3194
+ */
3195
+ declare function projectParticipant(driver: DriverParticipant, joinedAt: string): MeetParticipant;
3196
+ declare function createRoomEngine(options: RoomEngineOptions): RoomEngine;
3197
+ /** Exported for the host-power check in UI code — one definition, one rank. */
3198
+ declare function canModerate(participant: MeetParticipant | undefined): boolean;
3199
+ declare function hostUserIds(participants: readonly MeetParticipant[]): readonly UserId[];
3200
+
3201
+ /**
3202
+ * A PARTICIPANT'S OWN MEDIA CHOICES, REMEMBERED (Meet wave 2).
3203
+ *
3204
+ * Noise suppression and the camera background are personal and per device —
3205
+ * Zoom and Meet both remember them — so they live in this browser, never on a
3206
+ * server. Blocked storage means the defaults (suppression on, no background),
3207
+ * never a failed join.
3208
+ */
3209
+
3210
+ declare function createWebEffectsStorage(key?: string): EffectsStorage;
3211
+
3212
+ /**
3213
+ * THE IN-ROOM SERVER CALLS (Meet wave 2) — host moderation and the SAVED chat.
3214
+ *
3215
+ * 🚨 **Why these are server calls and not data packets.** Through 0.7.x a host's
3216
+ * "Mute" and "Remove" were published on the room's data plane (`mx.host`) and
3217
+ * nothing anywhere listened: the buttons did nothing. A client cannot be the
3218
+ * authority on host power, and a guest cannot write the database. So both go to
3219
+ * aidream (`POST /v1/meet/moderate`, `POST|GET /v1/meet/chat`), which decides the
3220
+ * actor's role from the meeting itself, performs the act through LiveKit's server
3221
+ * API, writes what is durable, and tells the room on its own `matrx.meet.*` topic.
3222
+ *
3223
+ * A guest proves who they are with the room pass this server signed for them, in
3224
+ * `x-meet-room-token` — a header, never a URL.
3225
+ */
3226
+
3227
+ /** Every host power the server performs. One vocabulary with `services/meet/moderation.py`. */
3228
+ type ModerationAction = "mute" | "stop_video" | "stop_screen" | "mute_all" | "ask_unmute" | "ask_all_unmute" | "spotlight" | "lower_hand" | "lower_all_hands" | "make_cohost" | "revoke_cohost" | "transfer_host" | "rename" | "remove" | "report";
3229
+ declare const MODERATION_ACTIONS: readonly ModerationAction[];
3230
+ interface ModerationRequest {
3231
+ readonly action: ModerationAction;
3232
+ readonly identity?: ParticipantIdentity | null | undefined;
3233
+ readonly name?: string | undefined;
3234
+ readonly reason?: string | undefined;
3235
+ }
3236
+ interface ModerationResult {
3237
+ readonly action: ModerationAction;
3238
+ readonly affected: readonly string[];
3239
+ /** The server's sentence for the actor. */
3240
+ readonly message: string;
3241
+ }
3242
+ interface InRoomClient {
3243
+ moderate(request: ModerationRequest): Promise<ModerationResult>;
3244
+ /** Send a message to EVERYONE. Saved; shows at once as pending, then as saved. */
3245
+ sendChat(text: string): Promise<void>;
3246
+ /** Read the saved chat once per join, so a late joiner sees what was said. */
3247
+ loadChat(): Promise<void>;
3248
+ }
3249
+ interface InRoomClientOptions {
3250
+ api: MeetApi;
3251
+ store: MeetStore;
3252
+ /** The room pass for THIS meeting, or null (a signed-in member needs none). */
3253
+ resolveRoomToken: () => string | null;
3254
+ now?: (() => number) | undefined;
3255
+ }
3256
+ declare function createInRoomClient(options: InRoomClientOptions): InRoomClient;
3257
+
3258
+ export { ATTENDANCE_CSV_HEADER, type AccessTokenSource, type 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, type CallId, type CallInvite, type CallInviteHandlerOptions, type CallInvitePayload, type CallMode, type CallState, type CallablePerson, type ConnectionQuality, 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, FILE_SESSION_PATH, 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_TRACK, type InRoomClient, type InRoomClientOptions, type InviteeInput, type InviteeRole, type JoinRequest, type JsonObject, type JsonValue, LISTEN_ONLY_NOTICE, type LibraryRecording, type LiveKitDriverOptions, type LobbyEntry, type LocalMediaState, type LocalTrackState, MEETING_INVITE_KIND, MEETING_INVITE_VERSION, MEET_ANSWER_CONTEXT_EVENT, MEET_ANSWER_EVENTS, MEET_EVENTS, MEET_ROUTES, MEET_SCHEMA, MODERATION_ACTIONS, type MediaDevicesLike, type MediaEffects, type MediaUnavailable, type MeetAi, type MeetAiAnswer, type MeetAiOptions, type MeetAnswerCoverage, type MeetApi, type MeetApiOptions, type MeetApiRequest, type MeetDevice, MeetError, type MeetErrorCode, type MeetEvent, type MeetFileMedia, type MeetGrants, type MeetIdentity, type MeetParticipant, type MeetRepository, type MeetRoomDriver, type MeetSnapshot, type MeetStore, type MeetingAttendee, type MeetingChanges, type MeetingChatHistory, type MeetingId, type MeetingInviteHandlerOptions, type MeetingInvitePayload, type MeetingInvitee, type MeetingKind, type MeetingNote, type MeetingNoteKind, type MeetingOccurrence, type MeetingRecord, type MeetingRecordBundle, type MeetingRecordRestriction, type MeetingRecordRestrictionNotice, type MeetingSearchHit, type MeetingSearchSource, type MeetingSession, type MeetingSessionOptions, type MeetingSettings, type ModerationAction, type ModerationNotice, type ModerationRequest, type ModerationResult, NOTE_TAKER_IDENTITY_PREFIX, NOTE_TAKER_METADATA_KEY, NO_GRANTS, type NameBook, type NoteTakerDispatch, type NoteTakerRefusal, type NoteTakerState, type NoteTakerStatus, ORGANIZATION_HEADER, type OccurrenceOverride, type OccurrenceState, type OrganizationId, PARTICIPANT_CAN_RECORD_ATTRIBUTE, PARTICIPANT_ROLE_ATTRIBUTE, type ParticipantIdentity, type ParticipantRole, REACTION_TTL_MS, REFUSED_ABSENCE, ROLE_RANK, ROOM_EVENTS, ROOM_EVENT_PREFIX, ROOM_TOKEN_HEADER, RPCS, type RecordingController, type RecordingControllerOptions, type RecordingPolicy, type RecordingRecord, type RecordingState, type RepositoryOptions, type RoomChatMessage, type RoomDriverFactory, type RoomDriverHandlers, type RoomEngine, type RoomEngineOptions, type RoomEventOutcome, type RoomEventTopic, type RoomName, type RoomPhase, type RoomReaction, type RoomToken, type RoomTokenStorage, type RsvpAnswer, type RsvpInvitation, type RsvpState, type ScheduleMeetingInput, type Stage, type StageArgs, type StageLayout, TABLES, TRANSCRIPTION_ATTRIBUTES, TRANSCRIPTION_TOPIC, type TokenClient, type TokenClientOptions, type TokenRequest, type TranscriptBlock, type TranscriptFormat, type TranscriptMatch, type TranscriptSegment, type TranscriptSource, type UpcomingOccurrence, type UserId, type VideoEffect, type WrapUpAnnouncement, type WrapUpStatus, absenceSentence, activeWrapUpSummary, announceMeeting, applyRoomEvent, asCallId, asMeetingId, asOrganizationId, asParticipantIdentity, asRoomName, asUserId, attendanceCsv, attendanceReport, attendanceTotals, callInviteAction, callInviteText, callStateLabel, canModerate, canModerateParticipant, canTransition, captionSegment, changesPatch, clockPosition, computeStage, countLabel, createCallCenter, createCallInviteHandler, createDeviceManager, createInRoomClient, createLiveKitDriver, createMeetAi, createMeetApi, createMeetRepository, createMeetStore, createMeetingInviteHandler, createMeetingSession, createMemoryDeviceStorage, createRecordingController, createRoomEngine, createTokenClient, createWebDeviceStorage, createWebEffectsStorage, createWebRoomTokenStorage, displayNameFor, fileDownloadPath, gridColumns, groupTranscript, guestRecordAccessExplanation, guestRecordAccessLabel, guestRecordAccessSetting, hasEnded, hostUserIds, invalidResponse, isGuest, isGuestIdentity, isHostRole, isListenOnly, isNoteTaker, isRealName, isRoomEventTopic, learnName, learnNames, listenOnlyNotice, loadFollowUpDraft, loadGuestMeetingRecord, loadMeetingChat, loadMeetingRecord, mapConnectionState, mapQuality, meetingInviteAction, meetingLength, meetingLink, meetingPath, meetingSlug, meetingTopic, meetingWhen, mergeCaption, mergeRecord, normalizeDeviceError, normalizeMeetError, normalizeSpoken, noteTakerIdentity, noteTakerLabel, noteTakerParticipant, noteTakerStatus, notesDocument, orderParticipants, parseRoomToken, participantSummary, playableRecordingFileId, projectInvitee, projectLibraryRecording, projectNote, projectOccurrence, projectOverride, projectParticipant, projectRsvpInvitation, projectSearchHit, projectSegment, projectUpcoming, pushMeetingToGoogleCalendar, readServerRefusal, readableTranscript, recordingLabel, recordingNotice, recordingOffsetMs, roleOf, roomTopic, searchTranscript, sendFollowUp, setGuestRecordAccess, settingsPatch, snapshotParticipant, snippetRuns, summarizeMeetingRecordRestrictions, transcriptDocument, unavailableFrom, userTopic, wasRefused, wrapUpStatus };