@ai-matrx/meet 0.7.5 → 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/CHANGELOG.md +34 -0
- package/dist/index.cjs +569 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +540 -232
- package/dist/index.d.ts +540 -232
- package/dist/index.js +569 -20
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +543 -38
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +543 -38
- package/dist/react.js.map +1 -1
- package/dist/styles.css +8 -0
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -975,6 +975,8 @@ interface MeetSnapshot {
|
|
|
975
975
|
readonly chat: readonly RoomChatMessage[];
|
|
976
976
|
/** Whether the SAVED chat has been read for this meeting (late joiners see history). */
|
|
977
977
|
readonly chatLoaded: boolean;
|
|
978
|
+
/** Why the saved chat could not be read on join, in words — or null. */
|
|
979
|
+
readonly chatHistoryError: string | null;
|
|
978
980
|
readonly reactions: readonly RoomReaction[];
|
|
979
981
|
/**
|
|
980
982
|
* The participant a HOST spotlighted for everyone, from the room's metadata
|
|
@@ -1340,6 +1342,10 @@ declare const RPCS: {
|
|
|
1340
1342
|
readonly meetingOccurrences: "meet_meeting_occurrences";
|
|
1341
1343
|
/** Every occurrence the reader can see between two instants — the Upcoming list. */
|
|
1342
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";
|
|
1343
1349
|
};
|
|
1344
1350
|
type Row = Record<string, unknown>;
|
|
1345
1351
|
interface CreateCallInviteInput {
|
|
@@ -1426,6 +1432,17 @@ interface MeetRepository {
|
|
|
1426
1432
|
acknowledgedAt: string;
|
|
1427
1433
|
}): Promise<void>;
|
|
1428
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>;
|
|
1429
1446
|
scheduleMeeting(input: ScheduleMeetingInput): Promise<MeetingRecord>;
|
|
1430
1447
|
/** `expectedVersion` from the row you edited; a stale one is a `conflict`. */
|
|
1431
1448
|
updateMeeting(meetingId: MeetingId, changes: MeetingChanges, options?: {
|
|
@@ -1519,6 +1536,50 @@ declare function settingsPatch(settings: MeetingSettings): Record<string, unknow
|
|
|
1519
1536
|
*/
|
|
1520
1537
|
declare function changesPatch(changes: MeetingChanges): Record<string, unknown>;
|
|
1521
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
|
+
}[];
|
|
1522
1583
|
|
|
1523
1584
|
/**
|
|
1524
1585
|
* THE CALL CENTER — ringing, answering, and the six ways a call ends badly.
|
|
@@ -2264,6 +2325,15 @@ interface MeetingSessionOptions {
|
|
|
2264
2325
|
* to `engine.rejoin()`.
|
|
2265
2326
|
*/
|
|
2266
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;
|
|
2267
2337
|
}
|
|
2268
2338
|
interface MeetingSession {
|
|
2269
2339
|
/** Subscribe to the room's out-of-band plane. Hosts see knocks; guests see verdicts. */
|
|
@@ -2355,39 +2425,6 @@ declare function createRecordingController(options: RecordingControllerOptions):
|
|
|
2355
2425
|
/** The indicator's words. One definition, so no surface invents a softer one. */
|
|
2356
2426
|
declare function recordingLabel(state: RecordingState): string | null;
|
|
2357
2427
|
|
|
2358
|
-
/**
|
|
2359
|
-
* MEET WAVE 1 — the two server calls a management screen makes (0.8).
|
|
2360
|
-
*
|
|
2361
|
-
* Every WRITE (schedule, edit, reschedule, cancel, archive, occurrence change,
|
|
2362
|
-
* invite, answer) goes straight to the database through `MeetRepository` — the
|
|
2363
|
-
* database decides who may. What a browser cannot do is send an email with a
|
|
2364
|
-
* calendar object in it, so after any of those writes a screen calls
|
|
2365
|
-
* `announceMeeting`: the server tells each invitee exactly what they have not
|
|
2366
|
-
* been told (invitation, update or cancellation, each with its `.ics`), and
|
|
2367
|
-
* re-queues reminders. It is idempotent — call it after every save.
|
|
2368
|
-
*/
|
|
2369
|
-
|
|
2370
|
-
interface AnnounceResult {
|
|
2371
|
-
readonly invitations: number;
|
|
2372
|
-
readonly updates: number;
|
|
2373
|
-
readonly cancellations: number;
|
|
2374
|
-
readonly alreadyCurrent: number;
|
|
2375
|
-
readonly remindersQueued: number;
|
|
2376
|
-
readonly remindersCancelled: number;
|
|
2377
|
-
}
|
|
2378
|
-
/** Bring every invitee's inbox and calendar up to date. The host, a co-host or an editor. */
|
|
2379
|
-
declare function announceMeeting(api: MeetApi, meetingId: MeetingId): Promise<AnnounceResult>;
|
|
2380
|
-
/**
|
|
2381
|
-
* Put a one-off meeting on the caller's own connected Google Calendar. GATED by
|
|
2382
|
-
* the server to Google's review accounts while the Calendar scope is in review —
|
|
2383
|
-
* a refusal is a `forbidden` MeetError carrying the server's sentence. Everyone
|
|
2384
|
-
* already gets the meeting into any calendar through the invitation's `.ics`.
|
|
2385
|
-
*/
|
|
2386
|
-
declare function pushMeetingToGoogleCalendar(api: MeetApi, meetingId: MeetingId, target: {
|
|
2387
|
-
connectionId: string;
|
|
2388
|
-
calendarId?: string;
|
|
2389
|
-
}): Promise<Record<string, unknown>>;
|
|
2390
|
-
|
|
2391
2428
|
/**
|
|
2392
2429
|
* THE TOKEN FLOW — the ONLY authorization path into a room (R1).
|
|
2393
2430
|
*
|
|
@@ -2544,6 +2581,12 @@ declare const MEET_ROUTES: {
|
|
|
2544
2581
|
readonly meetingAnnounce: "/api/v1/meet/meetings/{meeting_id}/announce";
|
|
2545
2582
|
/** Put a one-off meeting on the caller's own Google Calendar (gated by the server). */
|
|
2546
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";
|
|
2547
2590
|
/**
|
|
2548
2591
|
* Meet wave 2: EVERY in-meeting host power (mute, stop video, mute all, ask to
|
|
2549
2592
|
* unmute, spotlight, lower hands, co-host, hand over, rename, remove, report).
|
|
@@ -2561,198 +2604,6 @@ declare const ROOM_TOKEN_HEADER = "x-meet-room-token";
|
|
|
2561
2604
|
declare function parseRoomToken(body: unknown, roomName: RoomName): RoomToken;
|
|
2562
2605
|
declare function createTokenClient(options: TokenClientOptions): TokenClient;
|
|
2563
2606
|
|
|
2564
|
-
/**
|
|
2565
|
-
* THE ROOM ENGINE — the object a host mounts once and never reasons about.
|
|
2566
|
-
*
|
|
2567
|
-
* It owns every sequence a calling product gets wrong: ask for a token, connect,
|
|
2568
|
-
* project the roster, carry the in-room control plane, keep the phase honest
|
|
2569
|
-
* through a reconnect, and tear everything down exactly once. A consumer's job
|
|
2570
|
-
* is `engine.join(...)` and rendering a snapshot.
|
|
2571
|
-
*
|
|
2572
|
-
* THREE rules are structural here, not documented-and-hoped:
|
|
2573
|
-
*
|
|
2574
|
-
* 1. **The phase never lies.** `connecting` becomes `connected` only when the
|
|
2575
|
-
* driver says so, and any failure becomes `failed` WITH the remedy attached
|
|
2576
|
-
* to the snapshot. There is no path that leaves a spinner running forever —
|
|
2577
|
-
* the single most common complaint about every video product.
|
|
2578
|
-
* 2. **Role comes from the TOKEN, never from the client.** A participant's role
|
|
2579
|
-
* is read out of the server-minted metadata. A client that could decide it
|
|
2580
|
-
* was host could mute anyone; here the claim is unforgeable because the
|
|
2581
|
-
* server signed it.
|
|
2582
|
-
* 3. **Teardown is idempotent and total.** Leaving twice, or unmounting during
|
|
2583
|
-
* a connect, disconnects once and leaves no listener behind. The half-torn
|
|
2584
|
-
* room is what produces "my camera light stays on after the call".
|
|
2585
|
-
*/
|
|
2586
|
-
|
|
2587
|
-
interface JoinRequest {
|
|
2588
|
-
readonly roomName: RoomName;
|
|
2589
|
-
readonly meetingId?: string | undefined;
|
|
2590
|
-
readonly slug?: string | undefined;
|
|
2591
|
-
/** `audio` joins with the camera off. Same room, same code path (D3). */
|
|
2592
|
-
readonly mode: CallMode;
|
|
2593
|
-
/**
|
|
2594
|
-
* Whether to ATTEMPT the microphone on join. Defaults to true. `false` is a
|
|
2595
|
-
* deliberate listen-only join — the pre-join screen's "Join with microphone"
|
|
2596
|
-
* toggle. Either way the join itself never depends on the outcome; a refused
|
|
2597
|
-
* device lands in `snapshot.media.microphone.unavailable`, not in a throw.
|
|
2598
|
-
*/
|
|
2599
|
-
readonly microphone?: boolean | undefined;
|
|
2600
|
-
}
|
|
2601
|
-
interface RoomEngineOptions {
|
|
2602
|
-
store: MeetStore;
|
|
2603
|
-
tokens: TokenClient;
|
|
2604
|
-
identity: MeetIdentity;
|
|
2605
|
-
driverFactory: RoomDriverFactory;
|
|
2606
|
-
/** Canonical public metadata read; room packets alone never authorize teardown. */
|
|
2607
|
-
resolveMeeting?: ((slug: string) => Promise<MeetingRecord>) | undefined;
|
|
2608
|
-
onDiagnostic?: ((event: {
|
|
2609
|
-
level: "info" | "warn" | "error";
|
|
2610
|
-
message: string;
|
|
2611
|
-
remedy?: string;
|
|
2612
|
-
}) => void) | undefined;
|
|
2613
|
-
/** Fired when the local participant leaves for any reason, including kicked. */
|
|
2614
|
-
onLeft?: ((reason: string | null) => void) | undefined;
|
|
2615
|
-
now?: (() => number) | undefined;
|
|
2616
|
-
/** Remembered noise suppression / background choice (Meet wave 2). */
|
|
2617
|
-
effectsStorage?: EffectsStorage | undefined;
|
|
2618
|
-
/**
|
|
2619
|
-
* The device the person PICKED for each kind (the pre-join pickers, the
|
|
2620
|
-
* settings panel). Applied before any track is created — through 0.7.x the
|
|
2621
|
-
* pickers stored a choice nothing ever read, so every join used the browser
|
|
2622
|
-
* default whatever the screen said.
|
|
2623
|
-
*/
|
|
2624
|
-
resolveDevice?: ((kind: "audioinput" | "videoinput" | "audiooutput") => string | null) | undefined;
|
|
2625
|
-
}
|
|
2626
|
-
interface RoomEngine {
|
|
2627
|
-
join(request: JoinRequest): Promise<void>;
|
|
2628
|
-
/**
|
|
2629
|
-
* 🚨 THE HOST SAID YES — COME IN.
|
|
2630
|
-
*
|
|
2631
|
-
* A lobby token is minted with `canPublish: false` and `canSubscribe: false`
|
|
2632
|
-
* (that is what makes the lobby real rather than a screen over a live
|
|
2633
|
-
* microphone), so admission is not a UI state change: the cached token has to
|
|
2634
|
-
* be thrown away and a NEW one minted with the grants the server now gives.
|
|
2635
|
-
* Without this the waiting room's own sentence — "you will join automatically
|
|
2636
|
-
* once you are admitted" — is a lie, and the guest waits forever.
|
|
2637
|
-
*/
|
|
2638
|
-
rejoin(): Promise<void>;
|
|
2639
|
-
leave(): Promise<void>;
|
|
2640
|
-
setMicrophoneEnabled(enabled: boolean): Promise<void>;
|
|
2641
|
-
setCameraEnabled(enabled: boolean): Promise<void>;
|
|
2642
|
-
setScreenShareEnabled(enabled: boolean): Promise<void>;
|
|
2643
|
-
raiseHand(raised: boolean): Promise<void>;
|
|
2644
|
-
react(emoji: string): Promise<void>;
|
|
2645
|
-
/**
|
|
2646
|
-
* A PRIVATE message to one person (Meet wave 2). Data plane only, addressed
|
|
2647
|
-
* to that identity, NEVER saved — see `core/chat.ts` for the saved,
|
|
2648
|
-
* everyone-chat, which goes through the server.
|
|
2649
|
-
*/
|
|
2650
|
-
sendPrivateChat(to: ParticipantIdentity, text: string): Promise<void>;
|
|
2651
|
-
/**
|
|
2652
|
-
* Change this participant's own media processing — noise suppression and the
|
|
2653
|
-
* camera background — on the live tracks, and remember it in this browser.
|
|
2654
|
-
* A refused effect is a diagnostic and a revert, never a lost camera.
|
|
2655
|
-
*/
|
|
2656
|
-
setEffects(next: Partial<MediaEffects>): Promise<void>;
|
|
2657
|
-
/** Whether this browser can run a background effect at all (absent, never dead). */
|
|
2658
|
-
supportsVideoEffects(): boolean;
|
|
2659
|
-
/** Choose the speaker (audio output) for this meeting. */
|
|
2660
|
-
switchDevice(kind: "audioinput" | "videoinput" | "audiooutput", deviceId: string): Promise<void>;
|
|
2661
|
-
pin(identity: ParticipantIdentity | null): void;
|
|
2662
|
-
setLayout(layout: StageLayout): void;
|
|
2663
|
-
attach(identity: ParticipantIdentity, kind: "camera" | "microphone" | "screen", element: HTMLMediaElement): () => void;
|
|
2664
|
-
token(): RoomToken | null;
|
|
2665
|
-
dispose(): Promise<void>;
|
|
2666
|
-
}
|
|
2667
|
-
/** Where this browser keeps a participant's media choices. */
|
|
2668
|
-
interface EffectsStorage {
|
|
2669
|
-
load(): MediaEffects;
|
|
2670
|
-
save(effects: MediaEffects): void;
|
|
2671
|
-
}
|
|
2672
|
-
/**
|
|
2673
|
-
* 🚨 THE SERVER'S WORD, ON TWO CARRIERS (MRI-A11, feedback `7418ef78`).
|
|
2674
|
-
*
|
|
2675
|
-
* aidream mints the role into the token's participant METADATA (a JSON blob)
|
|
2676
|
-
* AND as a LiveKit participant ATTRIBUTE. Both come out of one decision in
|
|
2677
|
-
* `services/meet/service.py`; neither can be written by a client. The attribute
|
|
2678
|
-
* is preferred because LiveKit delivers the attribute map on every roster
|
|
2679
|
-
* update, while a roster event can carry an empty metadata string — which is
|
|
2680
|
-
* precisely how, through 2026-09-08, EVERY participant projected as
|
|
2681
|
-
* "participant" and no host ever saw a host control in a browser.
|
|
2682
|
-
*
|
|
2683
|
-
* These strings are pinned on the server side too: `test_meet_service.py` reads
|
|
2684
|
-
* them out of THIS file and fails if the two sides drift.
|
|
2685
|
-
*/
|
|
2686
|
-
declare const PARTICIPANT_ROLE_ATTRIBUTE = "matrx.participant.role";
|
|
2687
|
-
declare const PARTICIPANT_CAN_RECORD_ATTRIBUTE = "matrx.participant.can_record";
|
|
2688
|
-
/**
|
|
2689
|
-
* Metadata is SERVER-MINTED and therefore trusted; it is still parsed
|
|
2690
|
-
* defensively, because a malformed blob must degrade a tile to "participant"
|
|
2691
|
-
* rather than throw inside a roster event and freeze the whole grid.
|
|
2692
|
-
*/
|
|
2693
|
-
declare function projectParticipant(driver: DriverParticipant, joinedAt: string): MeetParticipant;
|
|
2694
|
-
declare function createRoomEngine(options: RoomEngineOptions): RoomEngine;
|
|
2695
|
-
/** Exported for the host-power check in UI code — one definition, one rank. */
|
|
2696
|
-
declare function canModerate(participant: MeetParticipant | undefined): boolean;
|
|
2697
|
-
declare function hostUserIds(participants: readonly MeetParticipant[]): readonly UserId[];
|
|
2698
|
-
|
|
2699
|
-
/**
|
|
2700
|
-
* A PARTICIPANT'S OWN MEDIA CHOICES, REMEMBERED (Meet wave 2).
|
|
2701
|
-
*
|
|
2702
|
-
* Noise suppression and the camera background are personal and per device —
|
|
2703
|
-
* Zoom and Meet both remember them — so they live in this browser, never on a
|
|
2704
|
-
* server. Blocked storage means the defaults (suppression on, no background),
|
|
2705
|
-
* never a failed join.
|
|
2706
|
-
*/
|
|
2707
|
-
|
|
2708
|
-
declare function createWebEffectsStorage(key?: string): EffectsStorage;
|
|
2709
|
-
|
|
2710
|
-
/**
|
|
2711
|
-
* THE IN-ROOM SERVER CALLS (Meet wave 2) — host moderation and the SAVED chat.
|
|
2712
|
-
*
|
|
2713
|
-
* 🚨 **Why these are server calls and not data packets.** Through 0.7.x a host's
|
|
2714
|
-
* "Mute" and "Remove" were published on the room's data plane (`mx.host`) and
|
|
2715
|
-
* nothing anywhere listened: the buttons did nothing. A client cannot be the
|
|
2716
|
-
* authority on host power, and a guest cannot write the database. So both go to
|
|
2717
|
-
* aidream (`POST /v1/meet/moderate`, `POST|GET /v1/meet/chat`), which decides the
|
|
2718
|
-
* actor's role from the meeting itself, performs the act through LiveKit's server
|
|
2719
|
-
* API, writes what is durable, and tells the room on its own `matrx.meet.*` topic.
|
|
2720
|
-
*
|
|
2721
|
-
* A guest proves who they are with the room pass this server signed for them, in
|
|
2722
|
-
* `x-meet-room-token` — a header, never a URL.
|
|
2723
|
-
*/
|
|
2724
|
-
|
|
2725
|
-
/** Every host power the server performs. One vocabulary with `services/meet/moderation.py`. */
|
|
2726
|
-
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";
|
|
2727
|
-
declare const MODERATION_ACTIONS: readonly ModerationAction[];
|
|
2728
|
-
interface ModerationRequest {
|
|
2729
|
-
readonly action: ModerationAction;
|
|
2730
|
-
readonly identity?: ParticipantIdentity | null | undefined;
|
|
2731
|
-
readonly name?: string | undefined;
|
|
2732
|
-
readonly reason?: string | undefined;
|
|
2733
|
-
}
|
|
2734
|
-
interface ModerationResult {
|
|
2735
|
-
readonly action: ModerationAction;
|
|
2736
|
-
readonly affected: readonly string[];
|
|
2737
|
-
/** The server's sentence for the actor. */
|
|
2738
|
-
readonly message: string;
|
|
2739
|
-
}
|
|
2740
|
-
interface InRoomClient {
|
|
2741
|
-
moderate(request: ModerationRequest): Promise<ModerationResult>;
|
|
2742
|
-
/** Send a message to EVERYONE. Saved; shows at once as pending, then as saved. */
|
|
2743
|
-
sendChat(text: string): Promise<void>;
|
|
2744
|
-
/** Read the saved chat once per join, so a late joiner sees what was said. */
|
|
2745
|
-
loadChat(): Promise<void>;
|
|
2746
|
-
}
|
|
2747
|
-
interface InRoomClientOptions {
|
|
2748
|
-
api: MeetApi;
|
|
2749
|
-
store: MeetStore;
|
|
2750
|
-
/** The room pass for THIS meeting, or null (a signed-in member needs none). */
|
|
2751
|
-
resolveRoomToken: () => string | null;
|
|
2752
|
-
now?: (() => number) | undefined;
|
|
2753
|
-
}
|
|
2754
|
-
declare function createInRoomClient(options: InRoomClientOptions): InRoomClient;
|
|
2755
|
-
|
|
2756
2607
|
/**
|
|
2757
2608
|
* THE MEETING RECORD — what a meeting IS once the room is gone (MRI-D2).
|
|
2758
2609
|
*
|
|
@@ -2789,6 +2640,12 @@ interface MeetingRecordRestriction {
|
|
|
2789
2640
|
readonly message: string;
|
|
2790
2641
|
readonly remedy: string;
|
|
2791
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
|
+
}
|
|
2792
2649
|
/** A run of consecutive lines by one speaker — how a person reads a transcript. */
|
|
2793
2650
|
interface TranscriptBlock {
|
|
2794
2651
|
readonly id: string;
|
|
@@ -2822,6 +2679,12 @@ interface MeetingRecordBundle {
|
|
|
2822
2679
|
*/
|
|
2823
2680
|
readonly names: NameBook;
|
|
2824
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;
|
|
2825
2688
|
/**
|
|
2826
2689
|
* Load the record parts the reader may request; state all restrictions.
|
|
2827
2690
|
*
|
|
@@ -2854,13 +2717,56 @@ declare function wasRefused(bundle: MeetingRecordBundle, part: MeetingRecordRest
|
|
|
2854
2717
|
*/
|
|
2855
2718
|
declare function absenceSentence(bundle: MeetingRecordBundle, part: MeetingRecordRestriction["part"], absence: string): string;
|
|
2856
2719
|
/**
|
|
2857
|
-
*
|
|
2858
|
-
*
|
|
2859
|
-
*
|
|
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.
|
|
2860
2743
|
*/
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
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
|
|
2864
2770
|
* hit most and understand least. `processing` is not "broken" and `failed` is
|
|
2865
2771
|
* never silence: both say what happened and what to do.
|
|
2866
2772
|
*/
|
|
@@ -2911,6 +2817,14 @@ interface GuestRecordAccessResolution {
|
|
|
2911
2817
|
/** `meeting` when a host chose for this one; `organization` otherwise. */
|
|
2912
2818
|
readonly source: "meeting" | "organization";
|
|
2913
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[];
|
|
2914
2828
|
/**
|
|
2915
2829
|
* Read the record the server shares with THIS guest.
|
|
2916
2830
|
*
|
|
@@ -2947,4 +2861,398 @@ declare function setGuestRecordAccess(args: {
|
|
|
2947
2861
|
/** What a guest is told when the level shares nothing. Rendered, never hidden. */
|
|
2948
2862
|
declare const GUEST_RECORD_NOT_SHARED: Readonly<Record<"recording" | "transcript" | "notes" | "attendance", string>>;
|
|
2949
2863
|
|
|
2950
|
-
|
|
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 };
|