@ai-matrx/meet 0.7.2 → 0.7.5

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.js CHANGED
@@ -25,7 +25,7 @@ var MeetError = class extends Error {
25
25
  };
26
26
  var FORBIDDEN_CODES = /* @__PURE__ */ new Set(["42501", "PGRST301", "PGRST302"]);
27
27
  var NOT_FOUND_CODES = /* @__PURE__ */ new Set(["PGRST116", "PGRST205"]);
28
- var CONFLICT_CODES = /* @__PURE__ */ new Set(["23505"]);
28
+ var CONFLICT_CODES = /* @__PURE__ */ new Set(["23505", "40001", "55000"]);
29
29
  var SESSION_MARKERS = [
30
30
  "auth session missing",
31
31
  "jwt expired",
@@ -183,8 +183,28 @@ var MEET_ROUTES = {
183
183
  */
184
184
  record: "/api/v1/meet/record",
185
185
  /** A host chooses what guests may read of ONE meeting (MRF-1). */
186
- recordAccess: "/api/v1/meet/record/access"
186
+ recordAccess: "/api/v1/meet/record/access",
187
+ /**
188
+ * Meet wave 1 (0.8): bring every invitee up to date after a write — sends the
189
+ * invitation / update / cancellation emails with their `.ics` and re-queues
190
+ * reminders. Idempotent. `{meeting_id}` is substituted by `meetingPath`.
191
+ */
192
+ meetingAnnounce: "/api/v1/meet/meetings/{meeting_id}/announce",
193
+ /** Put a one-off meeting on the caller's own Google Calendar (gated by the server). */
194
+ meetingGoogleCalendar: "/api/v1/meet/meetings/{meeting_id}/google-calendar",
195
+ /**
196
+ * Meet wave 2: EVERY in-meeting host power (mute, stop video, mute all, ask to
197
+ * unmute, spotlight, lower hands, co-host, hand over, rename, remove, report).
198
+ * Decided by the server from the meeting itself; a guest's pass rides
199
+ * `x-meet-room-token`.
200
+ */
201
+ moderate: "/api/v1/meet/moderate",
202
+ /** Meet wave 2: the SAVED chat — `POST` sends to everyone, `GET ?meeting_id=` reads history. */
203
+ chat: "/api/v1/meet/chat"
187
204
  };
205
+ function meetingPath(template, meetingId) {
206
+ return template.replace("{meeting_id}", encodeURIComponent(meetingId));
207
+ }
188
208
  var ROOM_TOKEN_HEADER = "x-meet-room-token";
189
209
  var REFRESH_RATIO = 0.8;
190
210
  function readString(source, key) {
@@ -314,6 +334,19 @@ function createTokenClient(options) {
314
334
  "The link is wrong or the meeting was deleted. Show the 'meeting not found' screen; a durable link that 404s is never a retry."
315
335
  );
316
336
  }
337
+ if (response.status === 410) {
338
+ let reason = "This meeting was cancelled.";
339
+ try {
340
+ const body = await response.json();
341
+ if (typeof body?.detail?.message === "string") reason = body.detail.message;
342
+ } catch {
343
+ }
344
+ throw new MeetError(
345
+ "cancelled",
346
+ reason,
347
+ "Nobody can join a cancelled meeting. Show the cancelled screen with this sentence; ask the host to schedule a new one."
348
+ );
349
+ }
317
350
  if (response.status === 423) {
318
351
  throw new MeetError(
319
352
  "room-locked",
@@ -515,12 +548,12 @@ function createMeetAi(options) {
515
548
  async function dispatch(route, body, operation) {
516
549
  const response = await send({ path: route, body, operation, requireAuth: true });
517
550
  if (!response.ok) {
518
- const refusal = await refusalFor(response, operation);
519
- options.store.patch({ noteTakerRefusal: refusal });
551
+ const refusal2 = await refusalFor(response, operation);
552
+ options.store.patch({ noteTakerRefusal: refusal2 });
520
553
  throw new MeetError(
521
554
  response.status === 403 ? "forbidden" : "transport",
522
- refusal.reason,
523
- refusal.remedy
555
+ refusal2.reason,
556
+ refusal2.remedy
524
557
  );
525
558
  }
526
559
  options.store.patch({ noteTakerRefusal: null });
@@ -558,11 +591,11 @@ function createMeetAi(options) {
558
591
  ...signal !== void 0 ? { signal } : {}
559
592
  });
560
593
  if (!response.ok) {
561
- const refusal = await refusalFor(response, "meet.ai.ask");
594
+ const refusal2 = await refusalFor(response, "meet.ai.ask");
562
595
  throw new MeetError(
563
596
  response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : "transport",
564
- refusal.reason,
565
- refusal.remedy
597
+ refusal2.reason,
598
+ refusal2.remedy
566
599
  );
567
600
  }
568
601
  const body = response.body;
@@ -671,14 +704,14 @@ function noteTakerStatus(args) {
671
704
  const row = typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
672
705
  const recorded = readState(row["state"]);
673
706
  const detail = typeof row["reason"] === "string" && row["reason"].length > 0 ? row["reason"] : null;
674
- const refusal = args.refusal ?? null;
707
+ const refusal2 = args.refusal ?? null;
675
708
  if (recorded === null) {
676
- return { state: "absent", detail, present, refusal };
709
+ return { state: "absent", detail, present, refusal: refusal2 };
677
710
  }
678
711
  if (recorded === "transcribing" && !present) {
679
- return { state: "handoff_pending", detail, present, refusal };
712
+ return { state: "handoff_pending", detail, present, refusal: refusal2 };
680
713
  }
681
- return { state: recorded, detail, present, refusal };
714
+ return { state: recorded, detail, present, refusal: refusal2 };
682
715
  }
683
716
  function noteTakerLabel(status) {
684
717
  switch (status.state) {
@@ -1091,7 +1124,11 @@ var TABLES = {
1091
1124
  invites: "meet_call_invites",
1092
1125
  recordings: "meet_recordings",
1093
1126
  transcripts: "meet_transcript_segments",
1094
- notes: "meet_notes"
1127
+ notes: "meet_notes",
1128
+ /** Wave 1 (0.8): who a meeting was sent to, and what they answered. */
1129
+ invitees: "meet_invitees",
1130
+ /** Wave 1 (0.8): one occurrence of a series cancelled or moved. */
1131
+ occurrenceOverrides: "meet_occurrence_overrides"
1095
1132
  };
1096
1133
  var RPCS = {
1097
1134
  /** Atomic: create the invite row and stamp its expiry in one statement. */
@@ -1107,10 +1144,41 @@ var RPCS = {
1107
1144
  admit: "meet_admit_participant",
1108
1145
  setLock: "meet_set_lock",
1109
1146
  endMeeting: "meet_end_meeting",
1110
- recordConsent: "meet_record_consent"
1147
+ recordConsent: "meet_record_consent",
1148
+ /*
1149
+ * ── MEET WAVE 1 — management (0.8). Every write is a SECURITY DEFINER door
1150
+ * authorized by the meeting's host or a real iam grant (editor to change,
1151
+ * admin = co-host to archive or name a co-host) — never an organization
1152
+ * match. After any of these writes, call `announceMeeting` so the server
1153
+ * sends invitations / updates / cancellations and re-queues reminders.
1154
+ */
1155
+ /** Schedule with every setting at create; unset settings come from the knobs. */
1156
+ scheduleMeeting: "meet_schedule_meeting",
1157
+ /** Edit / reschedule — a JSON patch with a version compare-and-swap. */
1158
+ updateMeeting: "meet_update_meeting",
1159
+ /** Cancel is a state (`cancelled_at`); the link keeps resolving. */
1160
+ cancelMeeting: "meet_cancel_meeting",
1161
+ /** Archive (soft delete) — the invitations and exceptions cascade. */
1162
+ archiveMeeting: "meet_archive_meeting",
1163
+ restoreMeeting: "meet_restore_meeting",
1164
+ /** Cancel, move or restore ONE occurrence of a series by its original start. */
1165
+ setOccurrence: "meet_set_occurrence",
1166
+ addInvitees: "meet_add_invitees",
1167
+ setInviteeRole: "meet_set_invitee_role",
1168
+ removeInvitee: "meet_remove_invitee",
1169
+ /** A signed-in invitee answers for themselves. */
1170
+ respond: "meet_respond",
1171
+ /** The emailed RSVP link (no account): read and answer. */
1172
+ invitationByToken: "meet_invitation_by_token",
1173
+ respondByToken: "meet_respond_by_token",
1174
+ /** One meeting's occurrences between two instants (INVOKER — the reader's RLS). */
1175
+ meetingOccurrences: "meet_meeting_occurrences",
1176
+ /** Every occurrence the reader can see between two instants — the Upcoming list. */
1177
+ occurrencesBetween: "meet_occurrences_between"
1111
1178
  };
1112
1179
  var VOID_RPCS = [RPCS.admit, RPCS.recordConsent];
1113
- var MEETING_COLUMNS = "id,organization_id,room_name,slug,title,kind,host_user_id,scheduled_for,scheduled_duration_minutes,started_at,ended_at,locked,lobby_enabled,recording_policy,ai_enabled,metadata";
1180
+ var MEETING_COLUMNS = "id,organization_id,room_name,slug,title,kind,host_user_id,scheduled_for,scheduled_duration_minutes,started_at,ended_at,locked,lobby_enabled,recording_policy,ai_enabled,metadata,time_zone,agenda,recurrence_rule,join_before_host,cancelled_at,cancellation_reason,deleted_at,calendar_sequence,version";
1181
+ var INVITEE_COLUMNS = "id,meeting_id,invitee_user_id,email,display_name,role,rsvp_state,responded_at,rsvp_note,last_sent_sequence,last_sent_at";
1114
1182
  var RECORDING_COLUMNS = "id,meeting_id,state,started_at,ended_at,started_by,file_id,failure_reason";
1115
1183
  var SEGMENT_COLUMNS = "id,meeting_id,identity,speaker,text,started_at,is_final,metadata";
1116
1184
  var NOTE_COLUMNS = "id,meeting_id,text,kind,assignee_user_id,created_at,metadata";
@@ -1208,6 +1276,138 @@ function projectNote(row) {
1208
1276
  source: "record"
1209
1277
  };
1210
1278
  }
1279
+ var RSVP_STATES = ["needs_action", "accepted", "declined", "tentative"];
1280
+ var OCCURRENCE_STATES = ["scheduled", "moved", "cancelled"];
1281
+ function rsvpState(value) {
1282
+ return RSVP_STATES.includes(value) ? value : "needs_action";
1283
+ }
1284
+ function num(row, key) {
1285
+ const value = row[key];
1286
+ return typeof value === "number" ? value : null;
1287
+ }
1288
+ function when(row, key) {
1289
+ const value = row[key];
1290
+ if (value instanceof Date) return value.toISOString();
1291
+ return text(row, key);
1292
+ }
1293
+ function projectInvitee(row) {
1294
+ const operation = "meet.projectInvitee";
1295
+ const userId = text(row, "invitee_user_id");
1296
+ return {
1297
+ id: requireText(row, "id", operation),
1298
+ meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
1299
+ userId: userId === null ? null : asUserId(userId),
1300
+ email: text(row, "email"),
1301
+ displayName: text(row, "display_name"),
1302
+ role: text(row, "role") === "cohost" ? "cohost" : "invitee",
1303
+ rsvpState: rsvpState(text(row, "rsvp_state")),
1304
+ respondedAt: when(row, "responded_at"),
1305
+ rsvpNote: text(row, "rsvp_note"),
1306
+ lastSentSequence: num(row, "last_sent_sequence"),
1307
+ lastSentAt: when(row, "last_sent_at")
1308
+ };
1309
+ }
1310
+ function projectOccurrence(row) {
1311
+ const operation = "meet.projectOccurrence";
1312
+ const state = text(row, "state");
1313
+ return {
1314
+ meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
1315
+ originalStart: when(row, "original_start") ?? requireText(row, "original_start", operation),
1316
+ occurrenceStart: when(row, "occurrence_start") ?? requireText(row, "occurrence_start", operation),
1317
+ durationMinutes: num(row, "duration_minutes"),
1318
+ // An unknown state is shown as scheduled, never dropped: the row is real.
1319
+ state: OCCURRENCE_STATES.includes(state) ? state : "scheduled",
1320
+ sequence: num(row, "sequence") ?? 0
1321
+ };
1322
+ }
1323
+ function projectUpcoming(row) {
1324
+ const operation = "meet.projectUpcoming";
1325
+ const kind = text(row, "kind");
1326
+ const myRole = text(row, "my_role");
1327
+ const myRsvp = text(row, "my_rsvp");
1328
+ return {
1329
+ ...projectOccurrence(row),
1330
+ organizationId: asOrganizationId(requireText(row, "organization_id", operation)),
1331
+ title: text(row, "title") ?? "Meeting",
1332
+ slug: requireText(row, "slug", operation),
1333
+ kind: kind === "scheduled" || kind === "recurring" ? kind : "instant",
1334
+ hostUserId: asUserId(requireText(row, "host_user_id", operation)),
1335
+ timeZone: text(row, "time_zone") ?? "UTC",
1336
+ meetingCancelled: bool(row, "meeting_cancelled", false),
1337
+ myRsvp: myRsvp === null ? null : rsvpState(myRsvp),
1338
+ myRole: myRole === "host" || myRole === "cohost" || myRole === "invitee" ? myRole : null
1339
+ };
1340
+ }
1341
+ function projectOverride(row) {
1342
+ const operation = "meet.projectOverride";
1343
+ return {
1344
+ id: requireText(row, "id", operation),
1345
+ meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
1346
+ originalStart: when(row, "original_start") ?? requireText(row, "original_start", operation),
1347
+ state: text(row, "state") === "moved" ? "moved" : "cancelled",
1348
+ newStart: when(row, "new_start"),
1349
+ newDurationMinutes: num(row, "new_duration_minutes"),
1350
+ reason: text(row, "reason"),
1351
+ calendarSequence: num(row, "calendar_sequence") ?? 0,
1352
+ deletedAt: when(row, "deleted_at")
1353
+ };
1354
+ }
1355
+ function projectRsvpInvitation(body) {
1356
+ const operation = "meet.projectRsvpInvitation";
1357
+ if (typeof body !== "object" || body === null) {
1358
+ throw invalidResponse(operation, "the RSVP door returned no object");
1359
+ }
1360
+ const row = body;
1361
+ if (row["ok"] !== true) {
1362
+ return {
1363
+ ok: false,
1364
+ message: text(row, "message") ?? "This invitation link is no longer valid \u2014 ask the host to send it again."
1365
+ };
1366
+ }
1367
+ const meeting = row["meeting"] ?? {};
1368
+ const invitee = row["invitee"] ?? {};
1369
+ return {
1370
+ ok: true,
1371
+ meeting: {
1372
+ title: text(meeting, "title") ?? "Meeting",
1373
+ slug: requireText(meeting, "slug", operation),
1374
+ scheduledFor: when(meeting, "scheduled_for"),
1375
+ durationMinutes: num(meeting, "duration_minutes"),
1376
+ timeZone: text(meeting, "time_zone") ?? "UTC",
1377
+ agenda: text(meeting, "agenda"),
1378
+ recurrenceRule: text(meeting, "recurrence_rule"),
1379
+ cancelledAt: when(meeting, "cancelled_at"),
1380
+ cancellationReason: text(meeting, "cancellation_reason"),
1381
+ endedAt: when(meeting, "ended_at")
1382
+ },
1383
+ invitee: {
1384
+ displayName: text(invitee, "display_name"),
1385
+ email: text(invitee, "email"),
1386
+ rsvpState: rsvpState(text(invitee, "rsvp_state")),
1387
+ respondedAt: when(invitee, "responded_at")
1388
+ }
1389
+ };
1390
+ }
1391
+ function settingsPatch(settings) {
1392
+ const patch = {};
1393
+ if (settings.lobbyEnabled !== void 0) patch["lobby_enabled"] = settings.lobbyEnabled;
1394
+ if (settings.recordingPolicy !== void 0) patch["recording_policy"] = settings.recordingPolicy;
1395
+ if (settings.aiEnabled !== void 0) patch["ai_enabled"] = settings.aiEnabled;
1396
+ if (settings.joinBeforeHost !== void 0) patch["join_before_host"] = settings.joinBeforeHost;
1397
+ return patch;
1398
+ }
1399
+ function changesPatch(changes) {
1400
+ const patch = settingsPatch(changes);
1401
+ if (changes.title !== void 0) patch["title"] = changes.title;
1402
+ if (changes.agenda !== void 0) patch["agenda"] = changes.agenda;
1403
+ if (changes.timeZone !== void 0) patch["time_zone"] = changes.timeZone;
1404
+ if (changes.scheduledFor !== void 0) patch["scheduled_for"] = changes.scheduledFor;
1405
+ if (changes.scheduledDurationMinutes !== void 0) {
1406
+ patch["scheduled_duration_minutes"] = changes.scheduledDurationMinutes;
1407
+ }
1408
+ if (changes.recurrenceRule !== void 0) patch["recurrence_rule"] = changes.recurrenceRule;
1409
+ return patch;
1410
+ }
1211
1411
  function createMeetRepository(options) {
1212
1412
  const db = options.client.schema(MEET_SCHEMA);
1213
1413
  async function guardVoid(operation, run) {
@@ -1277,7 +1477,16 @@ function createMeetRepository(options) {
1277
1477
  lobbyEnabled: bool(row, "lobby_enabled", true),
1278
1478
  recordingPolicy: policy === "disabled" || policy === "always-on" ? policy : "host-controlled",
1279
1479
  aiEnabled: bool(row, "ai_enabled", true),
1280
- metadata: typeof metadata === "object" && metadata !== null && !Array.isArray(metadata) ? metadata : {}
1480
+ metadata: typeof metadata === "object" && metadata !== null && !Array.isArray(metadata) ? metadata : {},
1481
+ timeZone: text(row, "time_zone") ?? "UTC",
1482
+ agenda: text(row, "agenda"),
1483
+ recurrenceRule: text(row, "recurrence_rule"),
1484
+ joinBeforeHost: bool(row, "join_before_host", true),
1485
+ cancelledAt: text(row, "cancelled_at"),
1486
+ cancellationReason: text(row, "cancellation_reason"),
1487
+ deletedAt: text(row, "deleted_at"),
1488
+ calendarSequence: typeof row["calendar_sequence"] === "number" ? row["calendar_sequence"] : 0,
1489
+ version: typeof row["version"] === "number" ? row["version"] : 1
1281
1490
  };
1282
1491
  }
1283
1492
  function projectRecording(row) {
@@ -1364,6 +1573,170 @@ function createMeetRepository(options) {
1364
1573
  projectNote,
1365
1574
  projectRecording,
1366
1575
  projectAttendee,
1576
+ projectInvitee,
1577
+ async scheduleMeeting(input) {
1578
+ requireOrg(input.organizationId, "meet.scheduleMeeting");
1579
+ const row = await guard(
1580
+ "meet.scheduleMeeting",
1581
+ () => db.rpc(RPCS.scheduleMeeting, {
1582
+ p_organization_id: input.organizationId,
1583
+ p_host_user_id: input.hostUserId,
1584
+ p_title: input.title,
1585
+ p_scheduled_for: input.scheduledFor,
1586
+ p_time_zone: input.timeZone,
1587
+ p_duration_minutes: input.durationMinutes ?? null,
1588
+ p_agenda: input.agenda ?? null,
1589
+ p_recurrence_rule: input.recurrenceRule ?? null,
1590
+ p_settings: settingsPatch(input.settings ?? {})
1591
+ })
1592
+ );
1593
+ return projectMeeting(row);
1594
+ },
1595
+ async updateMeeting(meetingId, changes, options2 = {}) {
1596
+ const row = await guard(
1597
+ "meet.updateMeeting",
1598
+ () => db.rpc(RPCS.updateMeeting, {
1599
+ p_meeting_id: meetingId,
1600
+ p_changes: changesPatch(changes),
1601
+ p_expected_version: options2.expectedVersion ?? null,
1602
+ p_by_user_id: options2.byUserId ?? null
1603
+ })
1604
+ );
1605
+ return projectMeeting(row);
1606
+ },
1607
+ async cancelMeeting(meetingId, reason, byUserId = null) {
1608
+ const row = await guard(
1609
+ "meet.cancelMeeting",
1610
+ () => db.rpc(RPCS.cancelMeeting, {
1611
+ p_meeting_id: meetingId,
1612
+ p_reason: reason,
1613
+ p_by_user_id: byUserId
1614
+ })
1615
+ );
1616
+ return projectMeeting(row);
1617
+ },
1618
+ async archiveMeeting(meetingId, byUserId = null) {
1619
+ const row = await guard(
1620
+ "meet.archiveMeeting",
1621
+ () => db.rpc(RPCS.archiveMeeting, { p_meeting_id: meetingId, p_by_user_id: byUserId })
1622
+ );
1623
+ return projectMeeting(row);
1624
+ },
1625
+ async restoreMeeting(meetingId, byUserId = null) {
1626
+ const row = await guard(
1627
+ "meet.restoreMeeting",
1628
+ () => db.rpc(RPCS.restoreMeeting, { p_meeting_id: meetingId, p_by_user_id: byUserId })
1629
+ );
1630
+ return projectMeeting(row);
1631
+ },
1632
+ async setOccurrence(args) {
1633
+ const row = await guard(
1634
+ "meet.setOccurrence",
1635
+ () => db.rpc(RPCS.setOccurrence, {
1636
+ p_meeting_id: args.meetingId,
1637
+ p_original_start: args.originalStart,
1638
+ p_action: args.action,
1639
+ p_new_start: args.newStart ?? null,
1640
+ p_new_duration_minutes: args.newDurationMinutes ?? null,
1641
+ p_reason: args.reason ?? null,
1642
+ p_by_user_id: args.byUserId ?? null
1643
+ })
1644
+ );
1645
+ return projectOverride(row);
1646
+ },
1647
+ async meetingOccurrences(meetingId, { from, to, limit = 200 }) {
1648
+ const rows = await guard(
1649
+ "meet.meetingOccurrences",
1650
+ () => db.rpc(RPCS.meetingOccurrences, {
1651
+ p_meeting_id: meetingId,
1652
+ p_from: from,
1653
+ p_to: to,
1654
+ p_limit: limit
1655
+ })
1656
+ );
1657
+ return rows.map(projectOccurrence);
1658
+ },
1659
+ async occurrencesBetween({ from, to, organizationId = null, limit = 500 }) {
1660
+ const rows = await guard(
1661
+ "meet.occurrencesBetween",
1662
+ () => db.rpc(RPCS.occurrencesBetween, {
1663
+ p_from: from,
1664
+ p_to: to,
1665
+ p_organization_id: organizationId,
1666
+ p_limit: limit
1667
+ })
1668
+ );
1669
+ return rows.map(projectUpcoming);
1670
+ },
1671
+ async invitees(meetingId) {
1672
+ const response = await db.from(TABLES.invitees).select(INVITEE_COLUMNS).eq("meeting_id", meetingId).is("deleted_at", null).order("created_at", { ascending: true }).order("id", { ascending: true });
1673
+ if (response.error !== null) throw normalizeMeetError(response.error, "meet.invitees");
1674
+ return (response.data ?? []).map(projectInvitee);
1675
+ },
1676
+ async addInvitees(meetingId, people, byUserId = null) {
1677
+ const rows = await guard(
1678
+ "meet.addInvitees",
1679
+ () => db.rpc(RPCS.addInvitees, {
1680
+ p_meeting_id: meetingId,
1681
+ p_invitees: people.map((person) => ({
1682
+ ...person.userId !== void 0 ? { user_id: person.userId } : {},
1683
+ ...person.email !== void 0 ? { email: person.email } : {},
1684
+ ...person.displayName !== void 0 ? { display_name: person.displayName } : {},
1685
+ ...person.role !== void 0 ? { role: person.role } : {}
1686
+ })),
1687
+ p_by_user_id: byUserId
1688
+ })
1689
+ );
1690
+ return rows.map(projectInvitee);
1691
+ },
1692
+ async setInviteeRole(inviteeId, role, byUserId = null) {
1693
+ const row = await guard(
1694
+ "meet.setInviteeRole",
1695
+ () => db.rpc(RPCS.setInviteeRole, {
1696
+ p_invitee_id: inviteeId,
1697
+ p_role: role,
1698
+ p_by_user_id: byUserId
1699
+ })
1700
+ );
1701
+ return projectInvitee(row);
1702
+ },
1703
+ async removeInvitee(inviteeId, byUserId = null) {
1704
+ const row = await guard(
1705
+ "meet.removeInvitee",
1706
+ () => db.rpc(RPCS.removeInvitee, { p_invitee_id: inviteeId, p_by_user_id: byUserId })
1707
+ );
1708
+ return projectInvitee(row);
1709
+ },
1710
+ async respond(meetingId, answer, note = null) {
1711
+ const row = await guard(
1712
+ "meet.respond",
1713
+ () => db.rpc(RPCS.respond, {
1714
+ p_meeting_id: meetingId,
1715
+ p_response: answer,
1716
+ p_note: note,
1717
+ p_by_user_id: null
1718
+ })
1719
+ );
1720
+ return projectInvitee(row);
1721
+ },
1722
+ async invitationByToken(secret) {
1723
+ const body = await guard(
1724
+ "meet.invitationByToken",
1725
+ () => db.rpc(RPCS.invitationByToken, { p_secret: secret })
1726
+ );
1727
+ return projectRsvpInvitation(body);
1728
+ },
1729
+ async respondByToken(secret, answer, note = null) {
1730
+ const body = await guard(
1731
+ "meet.respondByToken",
1732
+ () => db.rpc(RPCS.respondByToken, {
1733
+ p_secret: secret,
1734
+ p_response: answer,
1735
+ p_note: note
1736
+ })
1737
+ );
1738
+ return projectRsvpInvitation(body);
1739
+ },
1367
1740
  async attendees(meetingId) {
1368
1741
  const response = await db.from(TABLES.participants).select(ATTENDEE_COLUMNS).eq("meeting_id", meetingId).order("joined_at", { ascending: true }).order("id", { ascending: true });
1369
1742
  if (response.error !== null) throw normalizeMeetError(response.error, "meet.attendees");
@@ -1545,7 +1918,19 @@ var ROOM_EVENTS = {
1545
1918
  * production 2026-09-09 the guest lane showed no indicator at all while the
1546
1919
  * host recorded (MRI-C8).
1547
1920
  */
1548
- recording: "matrx.meet.recording"
1921
+ recording: "matrx.meet.recording",
1922
+ /**
1923
+ * A chat message to everyone was SAVED (Meet wave 2). Payload
1924
+ * `{ message: { id, meeting_id, sender_identity, sender_name, body, created_at } }`.
1925
+ */
1926
+ chat: "matrx.meet.chat",
1927
+ /**
1928
+ * A host or co-host acted (Meet wave 2). Payload `{ meeting_id, kind, … }`:
1929
+ * `muted {source, by}` · `ask-unmute {by}` · `removed {by}` — addressed to the
1930
+ * person concerned; `lower-hands {identities: [...] | "*"}` · `role {identity,
1931
+ * role}` — to everyone.
1932
+ */
1933
+ moderation: "matrx.meet.moderation"
1549
1934
  };
1550
1935
  function isRoomEventTopic(topic) {
1551
1936
  return topic.startsWith(ROOM_EVENT_PREFIX);
@@ -1581,6 +1966,10 @@ function applyRoomEvent(args) {
1581
1966
  return applyEnded(store, payload);
1582
1967
  case ROOM_EVENTS.recording:
1583
1968
  return applyRecording(store, payload);
1969
+ case ROOM_EVENTS.chat:
1970
+ return applyChat(store, payload);
1971
+ case ROOM_EVENTS.moderation:
1972
+ return applyModeration(store, payload, now);
1584
1973
  default:
1585
1974
  return ignored(`${topic} is not known to this version of @ai-matrx/meet`);
1586
1975
  }
@@ -1672,6 +2061,101 @@ function applyEnded(store, payload) {
1672
2061
  store.patch({ meeting: { ...meeting, endedAt } });
1673
2062
  return HANDLED;
1674
2063
  }
2064
+ function chatFromRow(row) {
2065
+ const id = text2(row["id"]);
2066
+ const identity = text2(row["sender_identity"]);
2067
+ const body = text2(row["body"]);
2068
+ if (id === null || identity === null || body === null) return null;
2069
+ return {
2070
+ id,
2071
+ identity: asParticipantIdentity(identity),
2072
+ displayName: text2(row["sender_name"]) ?? "Participant",
2073
+ text: body,
2074
+ at: text2(row["created_at"]) ?? (/* @__PURE__ */ new Date()).toISOString(),
2075
+ // `false` only from a server whose chat table has not landed: the room heard
2076
+ // it, nobody will see it later — and the line says so.
2077
+ saved: row["saved"] !== false
2078
+ };
2079
+ }
2080
+ function applyChat(store, payload) {
2081
+ const row = object(payload["message"]);
2082
+ if (row === null) return ignored("a chat event with no `message` row");
2083
+ const meeting = store.snapshot().meeting;
2084
+ if (meeting !== null && text2(row["meeting_id"]) !== null && row["meeting_id"] !== meeting.id) {
2085
+ return ignored("a chat message for a different meeting");
2086
+ }
2087
+ const message = chatFromRow(row);
2088
+ if (message === null) return ignored("a chat row with no id, sender or body");
2089
+ store.addChat(message);
2090
+ return HANDLED;
2091
+ }
2092
+ function moderationText(kind, payload) {
2093
+ const by = text2(payload["by"]) ?? "The host";
2094
+ switch (kind) {
2095
+ case "muted": {
2096
+ const source = text2(payload["source"]) ?? "microphone";
2097
+ return source === "microphone" ? `${by} muted you.` : source === "camera" ? `${by} turned off your camera.` : `${by} stopped your screen share.`;
2098
+ }
2099
+ case "ask-unmute":
2100
+ return `${by} is asking you to unmute.`;
2101
+ case "removed":
2102
+ return `${by} removed you from this meeting.`;
2103
+ default:
2104
+ return null;
2105
+ }
2106
+ }
2107
+ function applyModeration(store, payload, now) {
2108
+ const snapshot = store.snapshot();
2109
+ const meetingId = text2(payload["meeting_id"]);
2110
+ if (snapshot.meeting !== null && meetingId !== null && meetingId !== snapshot.meeting.id) {
2111
+ return ignored("a moderation event for a different meeting");
2112
+ }
2113
+ const kind = text2(payload["kind"]);
2114
+ switch (kind) {
2115
+ case "lower-hands": {
2116
+ const raw = payload["identities"];
2117
+ const everyone = raw === "*";
2118
+ const named = new Set(
2119
+ Array.isArray(raw) ? raw.filter((value) => typeof value === "string") : []
2120
+ );
2121
+ for (const participant of snapshot.participants) {
2122
+ if (participant.handRaised && (everyone || named.has(participant.identity))) {
2123
+ store.setHand(participant.identity, false);
2124
+ }
2125
+ }
2126
+ return HANDLED;
2127
+ }
2128
+ case "role": {
2129
+ const identity = text2(payload["identity"]);
2130
+ const role = text2(payload["role"]);
2131
+ if (identity === null || role === null || !ROLES.includes(role)) {
2132
+ return ignored("a role event with no identity or an unknown role");
2133
+ }
2134
+ if (identity === snapshot.localIdentity) {
2135
+ store.patch({
2136
+ grants: { ...snapshot.grants, role },
2137
+ moderationNotice: {
2138
+ kind: "role",
2139
+ text: role === "host" ? "You are now the host." : role === "cohost" ? "You are now a co-host." : "You are no longer a co-host.",
2140
+ at: now()
2141
+ }
2142
+ });
2143
+ }
2144
+ return HANDLED;
2145
+ }
2146
+ case "muted":
2147
+ case "ask-unmute":
2148
+ case "removed": {
2149
+ const sentence = moderationText(kind, payload);
2150
+ if (sentence === null) return ignored("a moderation event with no sentence");
2151
+ store.patch({ moderationNotice: { kind, text: sentence, at: now() } });
2152
+ return HANDLED;
2153
+ }
2154
+ default:
2155
+ return ignored(`a moderation kind this version does not know: ${String(kind)}`);
2156
+ }
2157
+ }
2158
+ var ROLES = ["host", "cohost", "participant", "guest"];
1675
2159
 
1676
2160
  // src/core/devices.ts
1677
2161
  var KINDS = ["audioinput", "videoinput", "audiooutput"];
@@ -1873,8 +2357,8 @@ function callStateLabel(state, outgoing) {
1873
2357
  return "Call";
1874
2358
  }
1875
2359
  }
1876
- function countLabel(count, singular, plural = `${singular}s`) {
1877
- return `${count} ${count === 1 ? singular : plural}`;
2360
+ function countLabel(count2, singular, plural = `${singular}s`) {
2361
+ return `${count2} ${count2 === 1 ? singular : plural}`;
1878
2362
  }
1879
2363
 
1880
2364
  // src/core/invitations.ts
@@ -2004,7 +2488,8 @@ function computeStage(args) {
2004
2488
  const ordered = orderParticipants(args.participants, args.activeSpeakers);
2005
2489
  const sharer = ordered.find((participant) => participant.screenSharing) ?? null;
2006
2490
  const pinnedParticipant = args.pinned === null ? null : ordered.find((participant) => participant.identity === args.pinned) ?? null;
2007
- const feature = sharer !== null ? sharer : args.layout === "grid" ? null : pinnedParticipant ?? ordered[0] ?? null;
2491
+ const spotlit = args.spotlight == null ? null : ordered.find((participant) => participant.identity === args.spotlight) ?? null;
2492
+ const feature = sharer !== null ? sharer : pinnedParticipant !== null && args.layout !== "grid" ? pinnedParticipant : spotlit !== null ? spotlit : args.layout === "grid" ? null : ordered[0] ?? null;
2008
2493
  const rest = feature === null ? ordered : ordered.filter((participant) => participant.identity !== feature.identity);
2009
2494
  const pageCount = Math.max(1, Math.ceil(rest.length / pageSize));
2010
2495
  const page = Math.min(Math.max(0, args.page ?? 0), pageCount - 1);
@@ -2018,11 +2503,11 @@ function computeStage(args) {
2018
2503
  presenting: sharer !== null
2019
2504
  };
2020
2505
  }
2021
- function gridColumns(count) {
2022
- if (count <= 1) return 1;
2023
- if (count <= 4) return 2;
2024
- if (count <= 9) return 3;
2025
- if (count <= 16) return 4;
2506
+ function gridColumns(count2) {
2507
+ if (count2 <= 1) return 1;
2508
+ if (count2 <= 4) return 2;
2509
+ if (count2 <= 9) return 3;
2510
+ if (count2 <= 16) return 4;
2026
2511
  return 5;
2027
2512
  }
2028
2513
 
@@ -2030,8 +2515,7 @@ function gridColumns(count) {
2030
2515
  var DATA_TOPICS = {
2031
2516
  hand: "mx.hand",
2032
2517
  reaction: "mx.reaction",
2033
- chat: "mx.chat",
2034
- hostAction: "mx.host"
2518
+ chat: "mx.chat"
2035
2519
  };
2036
2520
  var TRANSCRIPTION_TOPIC = "lk.transcription";
2037
2521
  var TRANSCRIPTION_ATTRIBUTES = {
@@ -2092,11 +2576,48 @@ function snapshotParticipant(participant, isLocal) {
2092
2576
  screenSharing: participant.isScreenShareEnabled
2093
2577
  };
2094
2578
  }
2579
+ var DEFAULT_AUDIO_PROCESSING = {
2580
+ noiseSuppression: true,
2581
+ echoCancellation: true,
2582
+ autoGainControl: true
2583
+ };
2584
+ function videoEffectsSupported() {
2585
+ const scope = globalThis;
2586
+ if (typeof scope.OffscreenCanvas === "undefined" || scope.document === void 0) return false;
2587
+ try {
2588
+ return scope.document.createElement("canvas").getContext("webgl2") !== null;
2589
+ } catch {
2590
+ return false;
2591
+ }
2592
+ }
2593
+ async function applyVideoEffect(target, effect) {
2594
+ const { Track } = await import("livekit-client");
2595
+ const track = target.localParticipant.getTrackPublication(Track.Source.Camera)?.track;
2596
+ if (track === void 0) return;
2597
+ try {
2598
+ if (effect.kind === "none") {
2599
+ await track.stopProcessor();
2600
+ return;
2601
+ }
2602
+ const processors = await import("@livekit/track-processors");
2603
+ const processor = effect.kind === "blur" ? processors.BackgroundBlur(effect.radius ?? 12) : processors.VirtualBackground(effect.imageUrl);
2604
+ await track.setProcessor(processor);
2605
+ } catch (error) {
2606
+ throw new MeetError(
2607
+ "media-unsupported",
2608
+ "The background effect could not start on this camera",
2609
+ "Background effects need a browser with WebGL2 (current Chrome, Edge, Firefox or Safari). Your video continues without the effect.",
2610
+ error
2611
+ );
2612
+ }
2613
+ }
2095
2614
  var encoder = new TextEncoder();
2096
2615
  var decoder = new TextDecoder();
2097
2616
  function createLiveKitDriver(handlers, options = {}) {
2098
2617
  let room = null;
2099
2618
  let projectRoster = null;
2619
+ let audioCapture = DEFAULT_AUDIO_PROCESSING;
2620
+ let videoEffect = { kind: "none" };
2100
2621
  async function load() {
2101
2622
  if (room !== null) return room;
2102
2623
  let module;
@@ -2125,7 +2646,9 @@ function createLiveKitDriver(handlers, options = {}) {
2125
2646
  handlers.participants([local, ...remotes]);
2126
2647
  };
2127
2648
  projectRoster = roster;
2128
- created.on(RoomEvent.ParticipantConnected, roster).on(RoomEvent.ParticipantDisconnected, roster).on(RoomEvent.TrackSubscribed, roster).on(RoomEvent.TrackUnsubscribed, roster).on(RoomEvent.TrackMuted, roster).on(RoomEvent.TrackUnmuted, roster).on(RoomEvent.LocalTrackPublished, roster).on(RoomEvent.LocalTrackUnpublished, roster).on(RoomEvent.ActiveSpeakersChanged, (speakers) => {
2649
+ created.on(RoomEvent.ParticipantConnected, roster).on(RoomEvent.ParticipantDisconnected, roster).on(RoomEvent.TrackSubscribed, roster).on(RoomEvent.TrackUnsubscribed, roster).on(RoomEvent.TrackMuted, roster).on(RoomEvent.TrackUnmuted, roster).on(RoomEvent.LocalTrackPublished, roster).on(RoomEvent.LocalTrackUnpublished, roster).on(RoomEvent.ParticipantNameChanged, roster).on(RoomEvent.ParticipantAttributesChanged, roster).on(RoomEvent.ParticipantMetadataChanged, roster).on(RoomEvent.RoomMetadataChanged, (metadata) => {
2650
+ handlers.roomMetadata?.(metadata ?? "");
2651
+ }).on(RoomEvent.ActiveSpeakersChanged, (speakers) => {
2129
2652
  handlers.activeSpeakers(speakers.map((speaker) => speaker.identity));
2130
2653
  }).on(RoomEvent.ConnectionQualityChanged, (quality, participant) => {
2131
2654
  handlers.quality(participant.identity, mapQuality(quality));
@@ -2202,6 +2725,7 @@ function createLiveKitDriver(handlers, options = {}) {
2202
2725
  throw normalizeDeviceError(error, "meet.connect");
2203
2726
  }
2204
2727
  projectRoster?.();
2728
+ handlers.roomMetadata?.(target.metadata ?? "");
2205
2729
  },
2206
2730
  async disconnect() {
2207
2731
  if (room === null) return;
@@ -2216,14 +2740,19 @@ function createLiveKitDriver(handlers, options = {}) {
2216
2740
  },
2217
2741
  async setMicrophoneEnabled(enabled) {
2218
2742
  try {
2219
- await requireRoom("setMicrophoneEnabled").localParticipant.setMicrophoneEnabled(enabled);
2743
+ await requireRoom("setMicrophoneEnabled").localParticipant.setMicrophoneEnabled(
2744
+ enabled,
2745
+ audioCapture
2746
+ );
2220
2747
  } catch (error) {
2221
2748
  throw normalizeDeviceError(error, "meet.setMicrophoneEnabled");
2222
2749
  }
2223
2750
  },
2224
2751
  async setCameraEnabled(enabled) {
2225
2752
  try {
2226
- await requireRoom("setCameraEnabled").localParticipant.setCameraEnabled(enabled);
2753
+ const target = requireRoom("setCameraEnabled");
2754
+ await target.localParticipant.setCameraEnabled(enabled);
2755
+ if (enabled && videoEffect.kind !== "none") await applyVideoEffect(target, videoEffect);
2227
2756
  } catch (error) {
2228
2757
  throw normalizeDeviceError(error, "meet.setCameraEnabled");
2229
2758
  }
@@ -2235,14 +2764,40 @@ function createLiveKitDriver(handlers, options = {}) {
2235
2764
  throw normalizeDeviceError(error, "meet.setScreenShareEnabled");
2236
2765
  }
2237
2766
  },
2238
- async publishData(topic, payload) {
2767
+ async publishData(topic, payload, to) {
2239
2768
  await requireRoom("publishData").localParticipant.publishData(
2240
2769
  // `reliable` is not optional for a control plane: a dropped
2241
2770
  // "hand lowered" leaves a hand up on every other screen forever.
2242
2771
  encoder.encode(payload),
2243
- { reliable: true, topic }
2772
+ to === void 0 || to.length === 0 ? { reliable: true, topic } : { reliable: true, topic, destinationIdentities: [...to] }
2244
2773
  );
2245
2774
  },
2775
+ async setAudioProcessing(processing) {
2776
+ audioCapture = { ...audioCapture, ...processing };
2777
+ const target = room;
2778
+ if (target === null) return;
2779
+ const { Track } = await import("livekit-client");
2780
+ const publication = target.localParticipant.getTrackPublication(Track.Source.Microphone);
2781
+ const track = publication?.track;
2782
+ if (track === void 0 || publication?.isMuted === true) return;
2783
+ try {
2784
+ await track.restartTrack({
2785
+ ...audioCapture,
2786
+ ...target.getActiveDevice("audioinput") !== void 0 ? { deviceId: target.getActiveDevice("audioinput") } : {}
2787
+ });
2788
+ } catch (error) {
2789
+ throw normalizeDeviceError(error, "meet.setAudioProcessing");
2790
+ }
2791
+ },
2792
+ async setVideoEffect(effect) {
2793
+ videoEffect = effect;
2794
+ const target = room;
2795
+ if (target === null) return;
2796
+ await applyVideoEffect(target, effect);
2797
+ },
2798
+ supportsVideoEffects() {
2799
+ return videoEffectsSupported();
2800
+ },
2246
2801
  attach(identity, kind, element) {
2247
2802
  const target = requireRoom("attach");
2248
2803
  const source = sources?.[kind];
@@ -2465,6 +3020,11 @@ function listenOnlyNotice(media) {
2465
3020
  }
2466
3021
 
2467
3022
  // src/core/store.ts
3023
+ var DEFAULT_EFFECTS = {
3024
+ noiseSuppression: true,
3025
+ background: "none",
3026
+ backgroundImage: null
3027
+ };
2468
3028
  var NO_GRANTS = {
2469
3029
  role: "participant",
2470
3030
  canRecord: false,
@@ -2487,7 +3047,12 @@ var EMPTY = {
2487
3047
  recordingSource: "local",
2488
3048
  recordingId: null,
2489
3049
  chat: [],
3050
+ chatLoaded: false,
2490
3051
  reactions: [],
3052
+ spotlight: null,
3053
+ moderationNotice: null,
3054
+ actionNotice: null,
3055
+ effects: DEFAULT_EFFECTS,
2491
3056
  transcript: [],
2492
3057
  notes: [],
2493
3058
  names: {},
@@ -2526,6 +3091,7 @@ function createMeetStore(initial = {}) {
2526
3091
  setParticipants(participants) {
2527
3092
  const names = learnNames(state.names, participants);
2528
3093
  const hands = new Map(state.participants.map((p) => [p.identity, p.handRaised]));
3094
+ const handTimes = new Map(state.participants.map((p) => [p.identity, p.handRaisedAt ?? null]));
2529
3095
  const quality = new Map(state.participants.map((p) => [p.identity, p.quality]));
2530
3096
  commit({
2531
3097
  ...state,
@@ -2539,6 +3105,7 @@ function createMeetStore(initial = {}) {
2539
3105
  // name book. `roster-names.test.ts` fails if a token ever gets past it.
2540
3106
  displayName: displayNameFor(names, participant.identity, participant.displayName),
2541
3107
  handRaised: hands.get(participant.identity) ?? participant.handRaised,
3108
+ handRaisedAt: handTimes.get(participant.identity) ?? participant.handRaisedAt ?? null,
2542
3109
  quality: participant.quality === "unknown" ? quality.get(participant.identity) ?? "unknown" : participant.quality
2543
3110
  }))
2544
3111
  });
@@ -2561,11 +3128,17 @@ function createMeetStore(initial = {}) {
2561
3128
  )
2562
3129
  });
2563
3130
  },
2564
- setHand(identity, raised) {
3131
+ setHand(identity, raised, at) {
2565
3132
  commit({
2566
3133
  ...state,
2567
3134
  participants: state.participants.map(
2568
- (participant) => participant.identity === identity ? { ...participant, handRaised: raised } : participant
3135
+ (participant) => participant.identity === identity ? {
3136
+ ...participant,
3137
+ handRaised: raised,
3138
+ // A hand that is ALREADY up keeps its place in the queue when
3139
+ // it is re-announced (to a late joiner): the first time wins.
3140
+ handRaisedAt: raised ? participant.handRaised && participant.handRaisedAt != null ? participant.handRaisedAt : at ?? Date.now() : null
3141
+ } : participant
2569
3142
  )
2570
3143
  });
2571
3144
  },
@@ -2575,6 +3148,31 @@ function createMeetStore(initial = {}) {
2575
3148
  const names = learnName(state.names, message.identity, message.displayName);
2576
3149
  commit({ ...state, chat: chat.slice(-CHAT_LIMIT), names });
2577
3150
  },
3151
+ loadChat(messages) {
3152
+ const known = new Set(state.chat.map((message) => message.id));
3153
+ const merged = [...messages.filter((message) => !known.has(message.id)), ...state.chat].sort(
3154
+ (a, b) => a.at < b.at ? -1 : a.at > b.at ? 1 : 0
3155
+ );
3156
+ const names = learnNames(
3157
+ state.names,
3158
+ messages.map((message) => ({ identity: message.identity, displayName: message.displayName }))
3159
+ );
3160
+ commit({ ...state, chat: merged.slice(-CHAT_LIMIT), chatLoaded: true, names });
3161
+ },
3162
+ settleChat(localId, next) {
3163
+ const index = state.chat.findIndex((message) => message.id === localId);
3164
+ if (index === -1) return;
3165
+ const current = state.chat[index];
3166
+ let replacement;
3167
+ if ("failed" in next && typeof next.failed === "string" && !("id" in next)) {
3168
+ replacement = { ...current, failed: next.failed };
3169
+ } else {
3170
+ replacement = next;
3171
+ }
3172
+ const duplicate = replacement.id !== localId && state.chat.some((message) => message.id === replacement.id);
3173
+ const chat = duplicate ? state.chat.filter((message) => message.id !== localId) : state.chat.map((message, position) => position === index ? replacement : message);
3174
+ commit({ ...state, chat });
3175
+ },
2578
3176
  addReaction(reaction) {
2579
3177
  commit({ ...state, reactions: [...state.reactions, reaction] });
2580
3178
  },
@@ -2665,7 +3263,7 @@ function createMeetStore(initial = {}) {
2665
3263
  commit({ ...state, media: { ...state.media, [kind]: next } });
2666
3264
  },
2667
3265
  reset() {
2668
- commit({ ...EMPTY });
3266
+ commit({ ...EMPTY, effects: state.effects });
2669
3267
  }
2670
3268
  };
2671
3269
  }
@@ -2995,11 +3593,11 @@ function createMeetingSession(options) {
2995
3593
  } catch {
2996
3594
  body = null;
2997
3595
  }
2998
- const refusal = readServerRefusal(body);
3596
+ const refusal2 = readServerRefusal(body);
2999
3597
  throw new MeetError(
3000
3598
  response.status === 403 ? "forbidden" : "transport",
3001
- refusal?.reason ?? `meet.endForEveryone: the server answered ${response.status}`,
3002
- refusal?.remedy !== void 0 && refusal.remedy.length > 0 ? refusal.remedy : "The meeting is still running. Only a host may end it, and the server re-checks that \u2014 being a host on this screen is not the decision."
3599
+ refusal2?.reason ?? `meet.endForEveryone: the server answered ${response.status}`,
3600
+ refusal2?.remedy !== void 0 && refusal2.remedy.length > 0 ? refusal2.remedy : "The meeting is still running. Only a host may end it, and the server re-checks that \u2014 being a host on this screen is not the decision."
3003
3601
  );
3004
3602
  }
3005
3603
  let payload = null;
@@ -3127,6 +3725,57 @@ function fileDownloadPath(fileId) {
3127
3725
  return `/files/${encodeURIComponent(fileId)}/download?inline=1`;
3128
3726
  }
3129
3727
 
3728
+ // src/core/management.ts
3729
+ async function refusal(response, operation) {
3730
+ let body = null;
3731
+ try {
3732
+ body = await response.json();
3733
+ } catch {
3734
+ body = null;
3735
+ }
3736
+ const said = readServerRefusal(body);
3737
+ return new MeetError(
3738
+ response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
3739
+ `${operation}: ${said?.reason ?? `the server answered ${response.status}`}`,
3740
+ said?.remedy || "The meeting itself is saved \u2014 only the telling failed. Try again; the call is idempotent."
3741
+ );
3742
+ }
3743
+ function count(body, key) {
3744
+ const value = body[key];
3745
+ return typeof value === "number" ? value : 0;
3746
+ }
3747
+ async function announceMeeting(api, meetingId) {
3748
+ const operation = "meet.announceMeeting";
3749
+ const response = await api.request({
3750
+ path: meetingPath(MEET_ROUTES.meetingAnnounce, meetingId),
3751
+ method: "POST",
3752
+ requireAuth: true,
3753
+ operation
3754
+ });
3755
+ if (!response.ok) throw await refusal(response, operation);
3756
+ const body = await response.json();
3757
+ return {
3758
+ invitations: count(body, "invitations"),
3759
+ updates: count(body, "updates"),
3760
+ cancellations: count(body, "cancellations"),
3761
+ alreadyCurrent: count(body, "already_current"),
3762
+ remindersQueued: count(body, "reminders_queued"),
3763
+ remindersCancelled: count(body, "reminders_cancelled")
3764
+ };
3765
+ }
3766
+ async function pushMeetingToGoogleCalendar(api, meetingId, target) {
3767
+ const operation = "meet.pushMeetingToGoogleCalendar";
3768
+ const response = await api.request({
3769
+ path: meetingPath(MEET_ROUTES.meetingGoogleCalendar, meetingId),
3770
+ method: "POST",
3771
+ requireAuth: true,
3772
+ operation,
3773
+ body: { connection_id: target.connectionId, calendar_id: target.calendarId ?? "primary" }
3774
+ });
3775
+ if (!response.ok) throw await refusal(response, operation);
3776
+ return await response.json();
3777
+ }
3778
+
3130
3779
  // src/core/room.ts
3131
3780
  var PARTICIPANT_ROLE_ATTRIBUTE = "matrx.participant.role";
3132
3781
  var PARTICIPANT_CAN_RECORD_ATTRIBUTE = "matrx.participant.can_record";
@@ -3246,7 +3895,37 @@ function createRoomEngine(options) {
3246
3895
  const handlers = {
3247
3896
  participants(list) {
3248
3897
  const joinedAt = new Date(now()).toISOString();
3898
+ const before = new Set(store.snapshot().participants.map((p) => p.identity));
3249
3899
  store.setParticipants(list.map((entry) => projectParticipant(entry, joinedAt)));
3900
+ const snapshot = store.snapshot();
3901
+ const newcomer = snapshot.participants.some(
3902
+ (participant) => !participant.isLocal && !before.has(participant.identity)
3903
+ );
3904
+ const me = snapshot.participants.find((p) => p.identity === snapshot.localIdentity);
3905
+ if (newcomer && before.size > 0 && me?.handRaised === true) {
3906
+ void publish(DATA_TOPICS.hand, { raised: true, raisedAt: me.handRaisedAt ?? now() }).catch(
3907
+ () => void 0
3908
+ );
3909
+ }
3910
+ },
3911
+ roomMetadata(metadata) {
3912
+ let spotlight = null;
3913
+ if (metadata.length > 0) {
3914
+ try {
3915
+ const parsed = JSON.parse(metadata);
3916
+ if (typeof parsed === "object" && parsed !== null) {
3917
+ const value = parsed.spotlight;
3918
+ if (typeof value === "string" && value.length > 0) spotlight = asParticipantIdentity(value);
3919
+ }
3920
+ } catch {
3921
+ report(
3922
+ "warn",
3923
+ "meet: the room's metadata is not JSON, so no spotlight was read from it",
3924
+ "Only aidream writes room metadata; check services/meet/moderation.py."
3925
+ );
3926
+ }
3927
+ }
3928
+ if (store.snapshot().spotlight !== spotlight) store.patch({ spotlight });
3250
3929
  },
3251
3930
  activeSpeakers(identities) {
3252
3931
  store.setSpeaking(identities.map(asParticipantIdentity));
@@ -3350,7 +4029,11 @@ function createRoomEngine(options) {
3350
4029
  const sender = asParticipantIdentity(from ?? "");
3351
4030
  switch (topic) {
3352
4031
  case DATA_TOPICS.hand:
3353
- store.setHand(sender, envelope.raised === true);
4032
+ store.setHand(
4033
+ sender,
4034
+ envelope.raised === true,
4035
+ typeof envelope.raisedAt === "number" ? envelope.raisedAt : now()
4036
+ );
3354
4037
  return;
3355
4038
  case DATA_TOPICS.reaction:
3356
4039
  if (typeof envelope.emoji === "string") {
@@ -3364,12 +4047,14 @@ function createRoomEngine(options) {
3364
4047
  return;
3365
4048
  case DATA_TOPICS.chat:
3366
4049
  if (typeof envelope.text === "string") {
4050
+ const local = store.snapshot().localIdentity;
3367
4051
  store.addChat({
3368
4052
  id: envelope.id ?? `${sender}-${now()}`,
3369
4053
  identity: sender,
3370
4054
  displayName: envelope.name ?? sender,
3371
4055
  text: envelope.text,
3372
- at: envelope.at ?? new Date(now()).toISOString()
4056
+ at: envelope.at ?? new Date(now()).toISOString(),
4057
+ ...typeof envelope.to === "string" && local !== null ? { to: local } : {}
3373
4058
  });
3374
4059
  }
3375
4060
  return;
@@ -3415,6 +4100,24 @@ function createRoomEngine(options) {
3415
4100
  store.patch({ phase: "connected", failure: null });
3416
4101
  store.setLocalTrack("microphone", IDLE_TRACK);
3417
4102
  store.setLocalTrack("camera", IDLE_TRACK);
4103
+ const effects = options.effectsStorage?.load() ?? store.snapshot().effects;
4104
+ store.patch({ effects });
4105
+ if (!effects.noiseSuppression || effects.background !== "none") {
4106
+ try {
4107
+ await applyEffects(created, effects);
4108
+ } catch {
4109
+ }
4110
+ }
4111
+ for (const kind of ["audioinput", "videoinput", "audiooutput"]) {
4112
+ const chosen = options.resolveDevice?.(kind) ?? null;
4113
+ if (chosen === null || chosen === "default") continue;
4114
+ try {
4115
+ await created.switchDevice(kind, chosen);
4116
+ } catch (error) {
4117
+ const normalized = normalizeDeviceError(error, `meet.switchDevice(${kind})`);
4118
+ report("warn", normalized.message, normalized.remedy);
4119
+ }
4120
+ }
3418
4121
  if (request.mode === "video") {
3419
4122
  await enableTrack(created, "camera", true);
3420
4123
  }
@@ -3437,9 +4140,30 @@ function createRoomEngine(options) {
3437
4140
  return state;
3438
4141
  }
3439
4142
  }
3440
- async function publish(topic, body) {
4143
+ async function publish(topic, body, to) {
3441
4144
  if (driver === null) return;
3442
- await driver.publishData(topic, JSON.stringify(body));
4145
+ await driver.publishData(topic, JSON.stringify(body), to);
4146
+ }
4147
+ async function applyEffects(target, effects) {
4148
+ try {
4149
+ await target.setAudioProcessing?.({
4150
+ noiseSuppression: effects.noiseSuppression,
4151
+ echoCancellation: true,
4152
+ autoGainControl: true
4153
+ });
4154
+ } catch (error) {
4155
+ report("warn", `meet: noise suppression could not be applied: ${String(error)}`, "The microphone keeps working without it.");
4156
+ }
4157
+ if (target.setVideoEffect === void 0) return;
4158
+ const effect = effects.background === "blur" ? { kind: "blur" } : effects.background === "image" && effects.backgroundImage !== null ? { kind: "image", imageUrl: effects.backgroundImage } : { kind: "none" };
4159
+ if (effect.kind !== "none" && target.supportsVideoEffects?.() === false) return;
4160
+ try {
4161
+ await target.setVideoEffect(effect);
4162
+ } catch (error) {
4163
+ const normalized = normalizeMeetError(error, "meet.setVideoEffect");
4164
+ report("warn", normalized.message, normalized.remedy);
4165
+ throw normalized;
4166
+ }
3443
4167
  }
3444
4168
  return {
3445
4169
  join(request) {
@@ -3495,7 +4219,7 @@ function createRoomEngine(options) {
3495
4219
  async raiseHand(raised) {
3496
4220
  const local = store.snapshot().localIdentity;
3497
4221
  if (local !== null) store.setHand(local, raised);
3498
- await publish(DATA_TOPICS.hand, { raised });
4222
+ await publish(DATA_TOPICS.hand, { raised, raisedAt: now() });
3499
4223
  },
3500
4224
  async react(emoji) {
3501
4225
  const local = store.snapshot().localIdentity;
@@ -3505,32 +4229,41 @@ function createRoomEngine(options) {
3505
4229
  }
3506
4230
  await publish(DATA_TOPICS.reaction, { id, emoji });
3507
4231
  },
3508
- async sendChat(text3) {
4232
+ async sendPrivateChat(to, text3) {
3509
4233
  const trimmed = text3.trim();
3510
4234
  if (trimmed.length === 0) return;
3511
4235
  const local = store.snapshot().localIdentity;
3512
4236
  const id = `${local ?? "local"}-${now()}`;
3513
4237
  const at = new Date(now()).toISOString();
3514
4238
  if (local !== null) {
3515
- store.addChat({
3516
- id,
3517
- identity: local,
3518
- displayName: identity.displayName,
3519
- text: trimmed,
3520
- at
3521
- });
4239
+ store.addChat({ id, identity: local, displayName: identity.displayName, text: trimmed, at, to });
3522
4240
  }
3523
- await publish(DATA_TOPICS.chat, { id, text: trimmed, at, name: identity.displayName });
4241
+ await publish(
4242
+ DATA_TOPICS.chat,
4243
+ { id, text: trimmed, at, name: identity.displayName, to },
4244
+ [to]
4245
+ );
3524
4246
  },
3525
- async publishHostAction(action) {
3526
- if (!isHostRole(effectiveRole(store.snapshot()))) {
3527
- throw new MeetError(
3528
- "forbidden",
3529
- `meet.hostAction(${action.kind}): this participant is not a host`,
3530
- "Host powers come from the server-minted token metadata. Hide the control instead of rendering a dead button (no dead ends)."
3531
- );
4247
+ async setEffects(next) {
4248
+ const previous = store.snapshot().effects;
4249
+ const merged = { ...previous, ...next };
4250
+ store.patch({ effects: merged });
4251
+ options.effectsStorage?.save(merged);
4252
+ if (driver === null) return;
4253
+ try {
4254
+ await applyEffects(driver, merged);
4255
+ } catch {
4256
+ const reverted = { ...merged, background: previous.background, backgroundImage: previous.backgroundImage };
4257
+ store.patch({ effects: reverted });
4258
+ options.effectsStorage?.save(reverted);
3532
4259
  }
3533
- await publish(DATA_TOPICS.hostAction, { ...action });
4260
+ },
4261
+ supportsVideoEffects() {
4262
+ return driver?.supportsVideoEffects?.() ?? false;
4263
+ },
4264
+ async switchDevice(kind, deviceId) {
4265
+ if (driver === null) return;
4266
+ await driver.switchDevice(kind, deviceId);
3534
4267
  },
3535
4268
  pin(who) {
3536
4269
  store.patch({ pinned: who, layout: who === null ? "grid" : "speaker" });
@@ -3561,6 +4294,192 @@ function hostUserIds(participants) {
3561
4294
  return participants.filter((participant) => participant.role === "host" && participant.userId !== null).map((participant) => participant.userId);
3562
4295
  }
3563
4296
 
4297
+ // src/core/effects.ts
4298
+ function coerce(value) {
4299
+ if (typeof value !== "object" || value === null) return DEFAULT_EFFECTS;
4300
+ const row = value;
4301
+ const background = row["background"] === "blur" || row["background"] === "image" ? row["background"] : "none";
4302
+ const backgroundImage = typeof row["backgroundImage"] === "string" ? row["backgroundImage"] : null;
4303
+ return {
4304
+ noiseSuppression: row["noiseSuppression"] !== false,
4305
+ background: background === "image" && backgroundImage === null ? "none" : background,
4306
+ backgroundImage
4307
+ };
4308
+ }
4309
+ function createWebEffectsStorage(key = "mx.meet.effects") {
4310
+ let memory = DEFAULT_EFFECTS;
4311
+ const storage = globalThis.localStorage;
4312
+ return {
4313
+ load() {
4314
+ if (storage === void 0) return memory;
4315
+ try {
4316
+ const raw = storage.getItem(key);
4317
+ return raw === null ? memory : coerce(JSON.parse(raw));
4318
+ } catch {
4319
+ return memory;
4320
+ }
4321
+ },
4322
+ save(effects) {
4323
+ memory = effects;
4324
+ try {
4325
+ storage?.setItem(key, JSON.stringify(effects));
4326
+ } catch {
4327
+ }
4328
+ }
4329
+ };
4330
+ }
4331
+
4332
+ // src/core/in-room.ts
4333
+ var MODERATION_ACTIONS = [
4334
+ "mute",
4335
+ "stop_video",
4336
+ "stop_screen",
4337
+ "mute_all",
4338
+ "ask_unmute",
4339
+ "ask_all_unmute",
4340
+ "spotlight",
4341
+ "lower_hand",
4342
+ "lower_all_hands",
4343
+ "make_cohost",
4344
+ "revoke_cohost",
4345
+ "transfer_host",
4346
+ "rename",
4347
+ "remove",
4348
+ "report"
4349
+ ];
4350
+ function createInRoomClient(options) {
4351
+ const { api, store } = options;
4352
+ const now = options.now ?? (() => Date.now());
4353
+ function meetingId(operation) {
4354
+ const meeting = store.snapshot().meeting;
4355
+ if (meeting === null) {
4356
+ throw new MeetError(
4357
+ "not-found",
4358
+ `${operation}: this room has no meeting record`,
4359
+ "Moderation and saved chat belong to a meeting. An ad-hoc call has neither."
4360
+ );
4361
+ }
4362
+ return meeting.id;
4363
+ }
4364
+ function headers() {
4365
+ const token = options.resolveRoomToken();
4366
+ return token === null ? void 0 : { [ROOM_TOKEN_HEADER]: token };
4367
+ }
4368
+ async function refusal2(response, operation, remedy) {
4369
+ let body = null;
4370
+ try {
4371
+ body = await response.json();
4372
+ } catch {
4373
+ body = null;
4374
+ }
4375
+ const said = readServerRefusal(body);
4376
+ return new MeetError(
4377
+ response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
4378
+ said?.reason ?? `${operation}: the server answered ${response.status}`,
4379
+ said?.remedy !== void 0 && said.remedy.length > 0 ? said.remedy : remedy
4380
+ );
4381
+ }
4382
+ return {
4383
+ async moderate(request) {
4384
+ const operation = `meet.moderate(${request.action})`;
4385
+ const hdrs = headers();
4386
+ const response = await api.request({
4387
+ path: MEET_ROUTES.moderate,
4388
+ method: "POST",
4389
+ operation,
4390
+ body: {
4391
+ meeting_id: meetingId(operation),
4392
+ action: request.action,
4393
+ ...request.identity != null ? { identity: request.identity } : {},
4394
+ ...request.name !== void 0 ? { name: request.name } : {},
4395
+ ...request.reason !== void 0 ? { reason: request.reason } : {}
4396
+ },
4397
+ ...hdrs !== void 0 ? { headers: hdrs } : {}
4398
+ });
4399
+ if (!response.ok) {
4400
+ throw await refusal2(
4401
+ response,
4402
+ operation,
4403
+ "Nothing was changed. The server decides host powers from the meeting itself."
4404
+ );
4405
+ }
4406
+ const payload = await response.json();
4407
+ return {
4408
+ action: request.action,
4409
+ affected: Array.isArray(payload["affected"]) ? payload["affected"].filter((value) => typeof value === "string") : [],
4410
+ message: typeof payload["message"] === "string" ? payload["message"] : "Done."
4411
+ };
4412
+ },
4413
+ async sendChat(text3) {
4414
+ const body = text3.trim();
4415
+ if (body.length === 0) return;
4416
+ const operation = "meet.sendChat";
4417
+ const id = meetingId(operation);
4418
+ const snapshot = store.snapshot();
4419
+ const me = snapshot.participants.find((p) => p.identity === snapshot.localIdentity);
4420
+ const localId = `pending-${now()}`;
4421
+ const pending = {
4422
+ id: localId,
4423
+ identity: snapshot.localIdentity ?? "local",
4424
+ displayName: me?.displayName ?? "You",
4425
+ text: body,
4426
+ at: new Date(now()).toISOString(),
4427
+ saved: false
4428
+ };
4429
+ store.addChat(pending);
4430
+ const hdrs = headers();
4431
+ let response;
4432
+ try {
4433
+ response = await api.request({
4434
+ path: MEET_ROUTES.chat,
4435
+ method: "POST",
4436
+ operation,
4437
+ body: { meeting_id: id, body },
4438
+ ...hdrs !== void 0 ? { headers: hdrs } : {}
4439
+ });
4440
+ } catch (error) {
4441
+ store.settleChat(localId, { failed: "Not sent \u2014 the server could not be reached." });
4442
+ throw error;
4443
+ }
4444
+ if (!response.ok) {
4445
+ const error = await refusal2(response, operation, "Your message was not sent.");
4446
+ store.settleChat(localId, { failed: error.message });
4447
+ throw error;
4448
+ }
4449
+ const saved = chatFromRow(await response.json());
4450
+ if (saved === null) {
4451
+ store.settleChat(localId, { failed: "The server's answer could not be read." });
4452
+ return;
4453
+ }
4454
+ store.settleChat(localId, saved);
4455
+ },
4456
+ async loadChat() {
4457
+ const operation = "meet.loadChat";
4458
+ const id = meetingId(operation);
4459
+ const hdrs = headers();
4460
+ const response = await api.request({
4461
+ path: `${MEET_ROUTES.chat}?meeting_id=${encodeURIComponent(id)}`,
4462
+ method: "GET",
4463
+ operation,
4464
+ ...hdrs !== void 0 ? { headers: hdrs } : {}
4465
+ });
4466
+ if (!response.ok) {
4467
+ throw await refusal2(
4468
+ response,
4469
+ operation,
4470
+ "Messages sent before you joined could not be read. New messages still arrive."
4471
+ );
4472
+ }
4473
+ const payload = await response.json();
4474
+ const rows = Array.isArray(payload.messages) ? payload.messages : [];
4475
+ const messages = rows.map(
4476
+ (row) => typeof row === "object" && row !== null ? chatFromRow(row) : null
4477
+ ).filter((message) => message !== null);
4478
+ store.loadChat(messages);
4479
+ }
4480
+ };
4481
+ }
4482
+
3564
4483
  // src/core/record.ts
3565
4484
  import { formatDurationMs } from "@ai-matrx/kit/format";
3566
4485
  function activeWrapUpSummary(notes) {
@@ -3701,7 +4620,7 @@ function meetingWhen(meeting, locale) {
3701
4620
  if (start === null) return "Time not recorded";
3702
4621
  const startedAt = new Date(start);
3703
4622
  if (Number.isNaN(startedAt.getTime())) return "Time not recorded";
3704
- const when = startedAt.toLocaleString(locale, {
4623
+ const when2 = startedAt.toLocaleString(locale, {
3705
4624
  weekday: "long",
3706
4625
  year: "numeric",
3707
4626
  month: "long",
@@ -3709,11 +4628,11 @@ function meetingWhen(meeting, locale) {
3709
4628
  hour: "numeric",
3710
4629
  minute: "2-digit"
3711
4630
  });
3712
- if (meeting.endedAt === null || meeting.startedAt === null) return when;
4631
+ if (meeting.endedAt === null || meeting.startedAt === null) return when2;
3713
4632
  const endedAt = new Date(meeting.endedAt);
3714
- if (Number.isNaN(endedAt.getTime())) return when;
4633
+ if (Number.isNaN(endedAt.getTime())) return when2;
3715
4634
  const elapsedMs = endedAt.getTime() - startedAt.getTime();
3716
- return `${when} \xB7 ${formatDurationMs(elapsedMs, { style: "long" })}`;
4635
+ return `${when2} \xB7 ${formatDurationMs(elapsedMs, { style: "long" })}`;
3717
4636
  }
3718
4637
  var GUEST_RECORD_ACCESS_LEVELS = [
3719
4638
  "none",
@@ -3789,8 +4708,8 @@ async function loadGuestMeetingRecord(args) {
3789
4708
  );
3790
4709
  }
3791
4710
  if (!response.ok) {
3792
- const refusal = await readRefusal(response);
3793
- return refusedBundle(meeting, refusal.message, refusal.remedy);
4711
+ const refusal2 = await readRefusal(response);
4712
+ return refusedBundle(meeting, refusal2.message, refusal2.remedy);
3794
4713
  }
3795
4714
  const body = await response.json();
3796
4715
  const payload = typeof body["data"] === "object" && body["data"] !== null ? body["data"] : body;
@@ -3881,8 +4800,8 @@ async function setGuestRecordAccess(args) {
3881
4800
  }
3882
4801
  });
3883
4802
  if (!response.ok) {
3884
- const refusal = await readRefusal(response);
3885
- throw new MeetError("forbidden", refusal.message, refusal.remedy);
4803
+ const refusal2 = await readRefusal(response);
4804
+ throw new MeetError("forbidden", refusal2.message, refusal2.remedy);
3886
4805
  }
3887
4806
  const body = await response.json();
3888
4807
  const payload = typeof body["data"] === "object" && body["data"] !== null ? body["data"] : body;
@@ -3897,6 +4816,7 @@ export {
3897
4816
  CALL_INVITE_VERSION,
3898
4817
  CONSENT_NOTICE_ATTRIBUTE,
3899
4818
  DATA_TOPICS,
4819
+ DEFAULT_EFFECTS,
3900
4820
  DEFAULT_PAGE_SIZE,
3901
4821
  END_MEETING_NOT_SERVED,
3902
4822
  FILE_SESSION_PATH,
@@ -3914,6 +4834,7 @@ export {
3914
4834
  MEET_EVENTS,
3915
4835
  MEET_ROUTES,
3916
4836
  MEET_SCHEMA,
4837
+ MODERATION_ACTIONS,
3917
4838
  MeetError,
3918
4839
  NOTE_TAKER_IDENTITY_PREFIX,
3919
4840
  NOTE_TAKER_METADATA_KEY,
@@ -3932,6 +4853,7 @@ export {
3932
4853
  TRANSCRIPTION_ATTRIBUTES,
3933
4854
  TRANSCRIPTION_TOPIC,
3934
4855
  absenceSentence,
4856
+ announceMeeting,
3935
4857
  applyRoomEvent,
3936
4858
  asCallId,
3937
4859
  asMeetingId,
@@ -3946,11 +4868,13 @@ export {
3946
4868
  canModerateParticipant,
3947
4869
  canTransition,
3948
4870
  captionSegment,
4871
+ changesPatch,
3949
4872
  computeStage,
3950
4873
  countLabel,
3951
4874
  createCallCenter,
3952
4875
  createCallInviteHandler,
3953
4876
  createDeviceManager,
4877
+ createInRoomClient,
3954
4878
  createLiveKitDriver,
3955
4879
  createMeetAi,
3956
4880
  createMeetApi,
@@ -3963,6 +4887,7 @@ export {
3963
4887
  createRoomEngine,
3964
4888
  createTokenClient,
3965
4889
  createWebDeviceStorage,
4890
+ createWebEffectsStorage,
3966
4891
  createWebRoomTokenStorage,
3967
4892
  displayNameFor,
3968
4893
  fileDownloadPath,
@@ -3990,6 +4915,7 @@ export {
3990
4915
  mapQuality,
3991
4916
  meetingInviteAction,
3992
4917
  meetingLink,
4918
+ meetingPath,
3993
4919
  meetingSlug,
3994
4920
  meetingTopic,
3995
4921
  meetingWhen,
@@ -4006,9 +4932,15 @@ export {
4006
4932
  parseRoomToken,
4007
4933
  participantSummary,
4008
4934
  playableRecordingFileId,
4935
+ projectInvitee,
4009
4936
  projectNote,
4937
+ projectOccurrence,
4938
+ projectOverride,
4010
4939
  projectParticipant,
4940
+ projectRsvpInvitation,
4011
4941
  projectSegment,
4942
+ projectUpcoming,
4943
+ pushMeetingToGoogleCalendar,
4012
4944
  readServerRefusal,
4013
4945
  readableTranscript,
4014
4946
  recordingLabel,
@@ -4016,6 +4948,7 @@ export {
4016
4948
  roleOf,
4017
4949
  roomTopic,
4018
4950
  setGuestRecordAccess,
4951
+ settingsPatch,
4019
4952
  snapshotParticipant,
4020
4953
  unavailableFrom,
4021
4954
  userTopic,