@ai-matrx/meet 0.7.2 → 0.7.3

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,19 @@ 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"
187
195
  };
196
+ function meetingPath(template, meetingId) {
197
+ return template.replace("{meeting_id}", encodeURIComponent(meetingId));
198
+ }
188
199
  var ROOM_TOKEN_HEADER = "x-meet-room-token";
189
200
  var REFRESH_RATIO = 0.8;
190
201
  function readString(source, key) {
@@ -314,6 +325,19 @@ function createTokenClient(options) {
314
325
  "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
326
  );
316
327
  }
328
+ if (response.status === 410) {
329
+ let reason = "This meeting was cancelled.";
330
+ try {
331
+ const body = await response.json();
332
+ if (typeof body?.detail?.message === "string") reason = body.detail.message;
333
+ } catch {
334
+ }
335
+ throw new MeetError(
336
+ "cancelled",
337
+ reason,
338
+ "Nobody can join a cancelled meeting. Show the cancelled screen with this sentence; ask the host to schedule a new one."
339
+ );
340
+ }
317
341
  if (response.status === 423) {
318
342
  throw new MeetError(
319
343
  "room-locked",
@@ -515,12 +539,12 @@ function createMeetAi(options) {
515
539
  async function dispatch(route, body, operation) {
516
540
  const response = await send({ path: route, body, operation, requireAuth: true });
517
541
  if (!response.ok) {
518
- const refusal = await refusalFor(response, operation);
519
- options.store.patch({ noteTakerRefusal: refusal });
542
+ const refusal2 = await refusalFor(response, operation);
543
+ options.store.patch({ noteTakerRefusal: refusal2 });
520
544
  throw new MeetError(
521
545
  response.status === 403 ? "forbidden" : "transport",
522
- refusal.reason,
523
- refusal.remedy
546
+ refusal2.reason,
547
+ refusal2.remedy
524
548
  );
525
549
  }
526
550
  options.store.patch({ noteTakerRefusal: null });
@@ -558,11 +582,11 @@ function createMeetAi(options) {
558
582
  ...signal !== void 0 ? { signal } : {}
559
583
  });
560
584
  if (!response.ok) {
561
- const refusal = await refusalFor(response, "meet.ai.ask");
585
+ const refusal2 = await refusalFor(response, "meet.ai.ask");
562
586
  throw new MeetError(
563
587
  response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : "transport",
564
- refusal.reason,
565
- refusal.remedy
588
+ refusal2.reason,
589
+ refusal2.remedy
566
590
  );
567
591
  }
568
592
  const body = response.body;
@@ -671,14 +695,14 @@ function noteTakerStatus(args) {
671
695
  const row = typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
672
696
  const recorded = readState(row["state"]);
673
697
  const detail = typeof row["reason"] === "string" && row["reason"].length > 0 ? row["reason"] : null;
674
- const refusal = args.refusal ?? null;
698
+ const refusal2 = args.refusal ?? null;
675
699
  if (recorded === null) {
676
- return { state: "absent", detail, present, refusal };
700
+ return { state: "absent", detail, present, refusal: refusal2 };
677
701
  }
678
702
  if (recorded === "transcribing" && !present) {
679
- return { state: "handoff_pending", detail, present, refusal };
703
+ return { state: "handoff_pending", detail, present, refusal: refusal2 };
680
704
  }
681
- return { state: recorded, detail, present, refusal };
705
+ return { state: recorded, detail, present, refusal: refusal2 };
682
706
  }
683
707
  function noteTakerLabel(status) {
684
708
  switch (status.state) {
@@ -1091,7 +1115,11 @@ var TABLES = {
1091
1115
  invites: "meet_call_invites",
1092
1116
  recordings: "meet_recordings",
1093
1117
  transcripts: "meet_transcript_segments",
1094
- notes: "meet_notes"
1118
+ notes: "meet_notes",
1119
+ /** Wave 1 (0.8): who a meeting was sent to, and what they answered. */
1120
+ invitees: "meet_invitees",
1121
+ /** Wave 1 (0.8): one occurrence of a series cancelled or moved. */
1122
+ occurrenceOverrides: "meet_occurrence_overrides"
1095
1123
  };
1096
1124
  var RPCS = {
1097
1125
  /** Atomic: create the invite row and stamp its expiry in one statement. */
@@ -1107,10 +1135,41 @@ var RPCS = {
1107
1135
  admit: "meet_admit_participant",
1108
1136
  setLock: "meet_set_lock",
1109
1137
  endMeeting: "meet_end_meeting",
1110
- recordConsent: "meet_record_consent"
1138
+ recordConsent: "meet_record_consent",
1139
+ /*
1140
+ * ── MEET WAVE 1 — management (0.8). Every write is a SECURITY DEFINER door
1141
+ * authorized by the meeting's host or a real iam grant (editor to change,
1142
+ * admin = co-host to archive or name a co-host) — never an organization
1143
+ * match. After any of these writes, call `announceMeeting` so the server
1144
+ * sends invitations / updates / cancellations and re-queues reminders.
1145
+ */
1146
+ /** Schedule with every setting at create; unset settings come from the knobs. */
1147
+ scheduleMeeting: "meet_schedule_meeting",
1148
+ /** Edit / reschedule — a JSON patch with a version compare-and-swap. */
1149
+ updateMeeting: "meet_update_meeting",
1150
+ /** Cancel is a state (`cancelled_at`); the link keeps resolving. */
1151
+ cancelMeeting: "meet_cancel_meeting",
1152
+ /** Archive (soft delete) — the invitations and exceptions cascade. */
1153
+ archiveMeeting: "meet_archive_meeting",
1154
+ restoreMeeting: "meet_restore_meeting",
1155
+ /** Cancel, move or restore ONE occurrence of a series by its original start. */
1156
+ setOccurrence: "meet_set_occurrence",
1157
+ addInvitees: "meet_add_invitees",
1158
+ setInviteeRole: "meet_set_invitee_role",
1159
+ removeInvitee: "meet_remove_invitee",
1160
+ /** A signed-in invitee answers for themselves. */
1161
+ respond: "meet_respond",
1162
+ /** The emailed RSVP link (no account): read and answer. */
1163
+ invitationByToken: "meet_invitation_by_token",
1164
+ respondByToken: "meet_respond_by_token",
1165
+ /** One meeting's occurrences between two instants (INVOKER — the reader's RLS). */
1166
+ meetingOccurrences: "meet_meeting_occurrences",
1167
+ /** Every occurrence the reader can see between two instants — the Upcoming list. */
1168
+ occurrencesBetween: "meet_occurrences_between"
1111
1169
  };
1112
1170
  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";
1171
+ 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";
1172
+ 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
1173
  var RECORDING_COLUMNS = "id,meeting_id,state,started_at,ended_at,started_by,file_id,failure_reason";
1115
1174
  var SEGMENT_COLUMNS = "id,meeting_id,identity,speaker,text,started_at,is_final,metadata";
1116
1175
  var NOTE_COLUMNS = "id,meeting_id,text,kind,assignee_user_id,created_at,metadata";
@@ -1208,6 +1267,138 @@ function projectNote(row) {
1208
1267
  source: "record"
1209
1268
  };
1210
1269
  }
1270
+ var RSVP_STATES = ["needs_action", "accepted", "declined", "tentative"];
1271
+ var OCCURRENCE_STATES = ["scheduled", "moved", "cancelled"];
1272
+ function rsvpState(value) {
1273
+ return RSVP_STATES.includes(value) ? value : "needs_action";
1274
+ }
1275
+ function num(row, key) {
1276
+ const value = row[key];
1277
+ return typeof value === "number" ? value : null;
1278
+ }
1279
+ function when(row, key) {
1280
+ const value = row[key];
1281
+ if (value instanceof Date) return value.toISOString();
1282
+ return text(row, key);
1283
+ }
1284
+ function projectInvitee(row) {
1285
+ const operation = "meet.projectInvitee";
1286
+ const userId = text(row, "invitee_user_id");
1287
+ return {
1288
+ id: requireText(row, "id", operation),
1289
+ meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
1290
+ userId: userId === null ? null : asUserId(userId),
1291
+ email: text(row, "email"),
1292
+ displayName: text(row, "display_name"),
1293
+ role: text(row, "role") === "cohost" ? "cohost" : "invitee",
1294
+ rsvpState: rsvpState(text(row, "rsvp_state")),
1295
+ respondedAt: when(row, "responded_at"),
1296
+ rsvpNote: text(row, "rsvp_note"),
1297
+ lastSentSequence: num(row, "last_sent_sequence"),
1298
+ lastSentAt: when(row, "last_sent_at")
1299
+ };
1300
+ }
1301
+ function projectOccurrence(row) {
1302
+ const operation = "meet.projectOccurrence";
1303
+ const state = text(row, "state");
1304
+ return {
1305
+ meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
1306
+ originalStart: when(row, "original_start") ?? requireText(row, "original_start", operation),
1307
+ occurrenceStart: when(row, "occurrence_start") ?? requireText(row, "occurrence_start", operation),
1308
+ durationMinutes: num(row, "duration_minutes"),
1309
+ // An unknown state is shown as scheduled, never dropped: the row is real.
1310
+ state: OCCURRENCE_STATES.includes(state) ? state : "scheduled",
1311
+ sequence: num(row, "sequence") ?? 0
1312
+ };
1313
+ }
1314
+ function projectUpcoming(row) {
1315
+ const operation = "meet.projectUpcoming";
1316
+ const kind = text(row, "kind");
1317
+ const myRole = text(row, "my_role");
1318
+ const myRsvp = text(row, "my_rsvp");
1319
+ return {
1320
+ ...projectOccurrence(row),
1321
+ organizationId: asOrganizationId(requireText(row, "organization_id", operation)),
1322
+ title: text(row, "title") ?? "Meeting",
1323
+ slug: requireText(row, "slug", operation),
1324
+ kind: kind === "scheduled" || kind === "recurring" ? kind : "instant",
1325
+ hostUserId: asUserId(requireText(row, "host_user_id", operation)),
1326
+ timeZone: text(row, "time_zone") ?? "UTC",
1327
+ meetingCancelled: bool(row, "meeting_cancelled", false),
1328
+ myRsvp: myRsvp === null ? null : rsvpState(myRsvp),
1329
+ myRole: myRole === "host" || myRole === "cohost" || myRole === "invitee" ? myRole : null
1330
+ };
1331
+ }
1332
+ function projectOverride(row) {
1333
+ const operation = "meet.projectOverride";
1334
+ return {
1335
+ id: requireText(row, "id", operation),
1336
+ meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
1337
+ originalStart: when(row, "original_start") ?? requireText(row, "original_start", operation),
1338
+ state: text(row, "state") === "moved" ? "moved" : "cancelled",
1339
+ newStart: when(row, "new_start"),
1340
+ newDurationMinutes: num(row, "new_duration_minutes"),
1341
+ reason: text(row, "reason"),
1342
+ calendarSequence: num(row, "calendar_sequence") ?? 0,
1343
+ deletedAt: when(row, "deleted_at")
1344
+ };
1345
+ }
1346
+ function projectRsvpInvitation(body) {
1347
+ const operation = "meet.projectRsvpInvitation";
1348
+ if (typeof body !== "object" || body === null) {
1349
+ throw invalidResponse(operation, "the RSVP door returned no object");
1350
+ }
1351
+ const row = body;
1352
+ if (row["ok"] !== true) {
1353
+ return {
1354
+ ok: false,
1355
+ message: text(row, "message") ?? "This invitation link is no longer valid \u2014 ask the host to send it again."
1356
+ };
1357
+ }
1358
+ const meeting = row["meeting"] ?? {};
1359
+ const invitee = row["invitee"] ?? {};
1360
+ return {
1361
+ ok: true,
1362
+ meeting: {
1363
+ title: text(meeting, "title") ?? "Meeting",
1364
+ slug: requireText(meeting, "slug", operation),
1365
+ scheduledFor: when(meeting, "scheduled_for"),
1366
+ durationMinutes: num(meeting, "duration_minutes"),
1367
+ timeZone: text(meeting, "time_zone") ?? "UTC",
1368
+ agenda: text(meeting, "agenda"),
1369
+ recurrenceRule: text(meeting, "recurrence_rule"),
1370
+ cancelledAt: when(meeting, "cancelled_at"),
1371
+ cancellationReason: text(meeting, "cancellation_reason"),
1372
+ endedAt: when(meeting, "ended_at")
1373
+ },
1374
+ invitee: {
1375
+ displayName: text(invitee, "display_name"),
1376
+ email: text(invitee, "email"),
1377
+ rsvpState: rsvpState(text(invitee, "rsvp_state")),
1378
+ respondedAt: when(invitee, "responded_at")
1379
+ }
1380
+ };
1381
+ }
1382
+ function settingsPatch(settings) {
1383
+ const patch = {};
1384
+ if (settings.lobbyEnabled !== void 0) patch["lobby_enabled"] = settings.lobbyEnabled;
1385
+ if (settings.recordingPolicy !== void 0) patch["recording_policy"] = settings.recordingPolicy;
1386
+ if (settings.aiEnabled !== void 0) patch["ai_enabled"] = settings.aiEnabled;
1387
+ if (settings.joinBeforeHost !== void 0) patch["join_before_host"] = settings.joinBeforeHost;
1388
+ return patch;
1389
+ }
1390
+ function changesPatch(changes) {
1391
+ const patch = settingsPatch(changes);
1392
+ if (changes.title !== void 0) patch["title"] = changes.title;
1393
+ if (changes.agenda !== void 0) patch["agenda"] = changes.agenda;
1394
+ if (changes.timeZone !== void 0) patch["time_zone"] = changes.timeZone;
1395
+ if (changes.scheduledFor !== void 0) patch["scheduled_for"] = changes.scheduledFor;
1396
+ if (changes.scheduledDurationMinutes !== void 0) {
1397
+ patch["scheduled_duration_minutes"] = changes.scheduledDurationMinutes;
1398
+ }
1399
+ if (changes.recurrenceRule !== void 0) patch["recurrence_rule"] = changes.recurrenceRule;
1400
+ return patch;
1401
+ }
1211
1402
  function createMeetRepository(options) {
1212
1403
  const db = options.client.schema(MEET_SCHEMA);
1213
1404
  async function guardVoid(operation, run) {
@@ -1277,7 +1468,16 @@ function createMeetRepository(options) {
1277
1468
  lobbyEnabled: bool(row, "lobby_enabled", true),
1278
1469
  recordingPolicy: policy === "disabled" || policy === "always-on" ? policy : "host-controlled",
1279
1470
  aiEnabled: bool(row, "ai_enabled", true),
1280
- metadata: typeof metadata === "object" && metadata !== null && !Array.isArray(metadata) ? metadata : {}
1471
+ metadata: typeof metadata === "object" && metadata !== null && !Array.isArray(metadata) ? metadata : {},
1472
+ timeZone: text(row, "time_zone") ?? "UTC",
1473
+ agenda: text(row, "agenda"),
1474
+ recurrenceRule: text(row, "recurrence_rule"),
1475
+ joinBeforeHost: bool(row, "join_before_host", true),
1476
+ cancelledAt: text(row, "cancelled_at"),
1477
+ cancellationReason: text(row, "cancellation_reason"),
1478
+ deletedAt: text(row, "deleted_at"),
1479
+ calendarSequence: typeof row["calendar_sequence"] === "number" ? row["calendar_sequence"] : 0,
1480
+ version: typeof row["version"] === "number" ? row["version"] : 1
1281
1481
  };
1282
1482
  }
1283
1483
  function projectRecording(row) {
@@ -1364,6 +1564,170 @@ function createMeetRepository(options) {
1364
1564
  projectNote,
1365
1565
  projectRecording,
1366
1566
  projectAttendee,
1567
+ projectInvitee,
1568
+ async scheduleMeeting(input) {
1569
+ requireOrg(input.organizationId, "meet.scheduleMeeting");
1570
+ const row = await guard(
1571
+ "meet.scheduleMeeting",
1572
+ () => db.rpc(RPCS.scheduleMeeting, {
1573
+ p_organization_id: input.organizationId,
1574
+ p_host_user_id: input.hostUserId,
1575
+ p_title: input.title,
1576
+ p_scheduled_for: input.scheduledFor,
1577
+ p_time_zone: input.timeZone,
1578
+ p_duration_minutes: input.durationMinutes ?? null,
1579
+ p_agenda: input.agenda ?? null,
1580
+ p_recurrence_rule: input.recurrenceRule ?? null,
1581
+ p_settings: settingsPatch(input.settings ?? {})
1582
+ })
1583
+ );
1584
+ return projectMeeting(row);
1585
+ },
1586
+ async updateMeeting(meetingId, changes, options2 = {}) {
1587
+ const row = await guard(
1588
+ "meet.updateMeeting",
1589
+ () => db.rpc(RPCS.updateMeeting, {
1590
+ p_meeting_id: meetingId,
1591
+ p_changes: changesPatch(changes),
1592
+ p_expected_version: options2.expectedVersion ?? null,
1593
+ p_by_user_id: options2.byUserId ?? null
1594
+ })
1595
+ );
1596
+ return projectMeeting(row);
1597
+ },
1598
+ async cancelMeeting(meetingId, reason, byUserId = null) {
1599
+ const row = await guard(
1600
+ "meet.cancelMeeting",
1601
+ () => db.rpc(RPCS.cancelMeeting, {
1602
+ p_meeting_id: meetingId,
1603
+ p_reason: reason,
1604
+ p_by_user_id: byUserId
1605
+ })
1606
+ );
1607
+ return projectMeeting(row);
1608
+ },
1609
+ async archiveMeeting(meetingId, byUserId = null) {
1610
+ const row = await guard(
1611
+ "meet.archiveMeeting",
1612
+ () => db.rpc(RPCS.archiveMeeting, { p_meeting_id: meetingId, p_by_user_id: byUserId })
1613
+ );
1614
+ return projectMeeting(row);
1615
+ },
1616
+ async restoreMeeting(meetingId, byUserId = null) {
1617
+ const row = await guard(
1618
+ "meet.restoreMeeting",
1619
+ () => db.rpc(RPCS.restoreMeeting, { p_meeting_id: meetingId, p_by_user_id: byUserId })
1620
+ );
1621
+ return projectMeeting(row);
1622
+ },
1623
+ async setOccurrence(args) {
1624
+ const row = await guard(
1625
+ "meet.setOccurrence",
1626
+ () => db.rpc(RPCS.setOccurrence, {
1627
+ p_meeting_id: args.meetingId,
1628
+ p_original_start: args.originalStart,
1629
+ p_action: args.action,
1630
+ p_new_start: args.newStart ?? null,
1631
+ p_new_duration_minutes: args.newDurationMinutes ?? null,
1632
+ p_reason: args.reason ?? null,
1633
+ p_by_user_id: args.byUserId ?? null
1634
+ })
1635
+ );
1636
+ return projectOverride(row);
1637
+ },
1638
+ async meetingOccurrences(meetingId, { from, to, limit = 200 }) {
1639
+ const rows = await guard(
1640
+ "meet.meetingOccurrences",
1641
+ () => db.rpc(RPCS.meetingOccurrences, {
1642
+ p_meeting_id: meetingId,
1643
+ p_from: from,
1644
+ p_to: to,
1645
+ p_limit: limit
1646
+ })
1647
+ );
1648
+ return rows.map(projectOccurrence);
1649
+ },
1650
+ async occurrencesBetween({ from, to, organizationId = null, limit = 500 }) {
1651
+ const rows = await guard(
1652
+ "meet.occurrencesBetween",
1653
+ () => db.rpc(RPCS.occurrencesBetween, {
1654
+ p_from: from,
1655
+ p_to: to,
1656
+ p_organization_id: organizationId,
1657
+ p_limit: limit
1658
+ })
1659
+ );
1660
+ return rows.map(projectUpcoming);
1661
+ },
1662
+ async invitees(meetingId) {
1663
+ 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 });
1664
+ if (response.error !== null) throw normalizeMeetError(response.error, "meet.invitees");
1665
+ return (response.data ?? []).map(projectInvitee);
1666
+ },
1667
+ async addInvitees(meetingId, people, byUserId = null) {
1668
+ const rows = await guard(
1669
+ "meet.addInvitees",
1670
+ () => db.rpc(RPCS.addInvitees, {
1671
+ p_meeting_id: meetingId,
1672
+ p_invitees: people.map((person) => ({
1673
+ ...person.userId !== void 0 ? { user_id: person.userId } : {},
1674
+ ...person.email !== void 0 ? { email: person.email } : {},
1675
+ ...person.displayName !== void 0 ? { display_name: person.displayName } : {},
1676
+ ...person.role !== void 0 ? { role: person.role } : {}
1677
+ })),
1678
+ p_by_user_id: byUserId
1679
+ })
1680
+ );
1681
+ return rows.map(projectInvitee);
1682
+ },
1683
+ async setInviteeRole(inviteeId, role, byUserId = null) {
1684
+ const row = await guard(
1685
+ "meet.setInviteeRole",
1686
+ () => db.rpc(RPCS.setInviteeRole, {
1687
+ p_invitee_id: inviteeId,
1688
+ p_role: role,
1689
+ p_by_user_id: byUserId
1690
+ })
1691
+ );
1692
+ return projectInvitee(row);
1693
+ },
1694
+ async removeInvitee(inviteeId, byUserId = null) {
1695
+ const row = await guard(
1696
+ "meet.removeInvitee",
1697
+ () => db.rpc(RPCS.removeInvitee, { p_invitee_id: inviteeId, p_by_user_id: byUserId })
1698
+ );
1699
+ return projectInvitee(row);
1700
+ },
1701
+ async respond(meetingId, answer, note = null) {
1702
+ const row = await guard(
1703
+ "meet.respond",
1704
+ () => db.rpc(RPCS.respond, {
1705
+ p_meeting_id: meetingId,
1706
+ p_response: answer,
1707
+ p_note: note,
1708
+ p_by_user_id: null
1709
+ })
1710
+ );
1711
+ return projectInvitee(row);
1712
+ },
1713
+ async invitationByToken(secret) {
1714
+ const body = await guard(
1715
+ "meet.invitationByToken",
1716
+ () => db.rpc(RPCS.invitationByToken, { p_secret: secret })
1717
+ );
1718
+ return projectRsvpInvitation(body);
1719
+ },
1720
+ async respondByToken(secret, answer, note = null) {
1721
+ const body = await guard(
1722
+ "meet.respondByToken",
1723
+ () => db.rpc(RPCS.respondByToken, {
1724
+ p_secret: secret,
1725
+ p_response: answer,
1726
+ p_note: note
1727
+ })
1728
+ );
1729
+ return projectRsvpInvitation(body);
1730
+ },
1367
1731
  async attendees(meetingId) {
1368
1732
  const response = await db.from(TABLES.participants).select(ATTENDEE_COLUMNS).eq("meeting_id", meetingId).order("joined_at", { ascending: true }).order("id", { ascending: true });
1369
1733
  if (response.error !== null) throw normalizeMeetError(response.error, "meet.attendees");
@@ -1873,8 +2237,8 @@ function callStateLabel(state, outgoing) {
1873
2237
  return "Call";
1874
2238
  }
1875
2239
  }
1876
- function countLabel(count, singular, plural = `${singular}s`) {
1877
- return `${count} ${count === 1 ? singular : plural}`;
2240
+ function countLabel(count2, singular, plural = `${singular}s`) {
2241
+ return `${count2} ${count2 === 1 ? singular : plural}`;
1878
2242
  }
1879
2243
 
1880
2244
  // src/core/invitations.ts
@@ -2018,11 +2382,11 @@ function computeStage(args) {
2018
2382
  presenting: sharer !== null
2019
2383
  };
2020
2384
  }
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;
2385
+ function gridColumns(count2) {
2386
+ if (count2 <= 1) return 1;
2387
+ if (count2 <= 4) return 2;
2388
+ if (count2 <= 9) return 3;
2389
+ if (count2 <= 16) return 4;
2026
2390
  return 5;
2027
2391
  }
2028
2392
 
@@ -2995,11 +3359,11 @@ function createMeetingSession(options) {
2995
3359
  } catch {
2996
3360
  body = null;
2997
3361
  }
2998
- const refusal = readServerRefusal(body);
3362
+ const refusal2 = readServerRefusal(body);
2999
3363
  throw new MeetError(
3000
3364
  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."
3365
+ refusal2?.reason ?? `meet.endForEveryone: the server answered ${response.status}`,
3366
+ 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
3367
  );
3004
3368
  }
3005
3369
  let payload = null;
@@ -3127,6 +3491,57 @@ function fileDownloadPath(fileId) {
3127
3491
  return `/files/${encodeURIComponent(fileId)}/download?inline=1`;
3128
3492
  }
3129
3493
 
3494
+ // src/core/management.ts
3495
+ async function refusal(response, operation) {
3496
+ let body = null;
3497
+ try {
3498
+ body = await response.json();
3499
+ } catch {
3500
+ body = null;
3501
+ }
3502
+ const said = readServerRefusal(body);
3503
+ return new MeetError(
3504
+ response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
3505
+ `${operation}: ${said?.reason ?? `the server answered ${response.status}`}`,
3506
+ said?.remedy || "The meeting itself is saved \u2014 only the telling failed. Try again; the call is idempotent."
3507
+ );
3508
+ }
3509
+ function count(body, key) {
3510
+ const value = body[key];
3511
+ return typeof value === "number" ? value : 0;
3512
+ }
3513
+ async function announceMeeting(api, meetingId) {
3514
+ const operation = "meet.announceMeeting";
3515
+ const response = await api.request({
3516
+ path: meetingPath(MEET_ROUTES.meetingAnnounce, meetingId),
3517
+ method: "POST",
3518
+ requireAuth: true,
3519
+ operation
3520
+ });
3521
+ if (!response.ok) throw await refusal(response, operation);
3522
+ const body = await response.json();
3523
+ return {
3524
+ invitations: count(body, "invitations"),
3525
+ updates: count(body, "updates"),
3526
+ cancellations: count(body, "cancellations"),
3527
+ alreadyCurrent: count(body, "already_current"),
3528
+ remindersQueued: count(body, "reminders_queued"),
3529
+ remindersCancelled: count(body, "reminders_cancelled")
3530
+ };
3531
+ }
3532
+ async function pushMeetingToGoogleCalendar(api, meetingId, target) {
3533
+ const operation = "meet.pushMeetingToGoogleCalendar";
3534
+ const response = await api.request({
3535
+ path: meetingPath(MEET_ROUTES.meetingGoogleCalendar, meetingId),
3536
+ method: "POST",
3537
+ requireAuth: true,
3538
+ operation,
3539
+ body: { connection_id: target.connectionId, calendar_id: target.calendarId ?? "primary" }
3540
+ });
3541
+ if (!response.ok) throw await refusal(response, operation);
3542
+ return await response.json();
3543
+ }
3544
+
3130
3545
  // src/core/room.ts
3131
3546
  var PARTICIPANT_ROLE_ATTRIBUTE = "matrx.participant.role";
3132
3547
  var PARTICIPANT_CAN_RECORD_ATTRIBUTE = "matrx.participant.can_record";
@@ -3701,7 +4116,7 @@ function meetingWhen(meeting, locale) {
3701
4116
  if (start === null) return "Time not recorded";
3702
4117
  const startedAt = new Date(start);
3703
4118
  if (Number.isNaN(startedAt.getTime())) return "Time not recorded";
3704
- const when = startedAt.toLocaleString(locale, {
4119
+ const when2 = startedAt.toLocaleString(locale, {
3705
4120
  weekday: "long",
3706
4121
  year: "numeric",
3707
4122
  month: "long",
@@ -3709,11 +4124,11 @@ function meetingWhen(meeting, locale) {
3709
4124
  hour: "numeric",
3710
4125
  minute: "2-digit"
3711
4126
  });
3712
- if (meeting.endedAt === null || meeting.startedAt === null) return when;
4127
+ if (meeting.endedAt === null || meeting.startedAt === null) return when2;
3713
4128
  const endedAt = new Date(meeting.endedAt);
3714
- if (Number.isNaN(endedAt.getTime())) return when;
4129
+ if (Number.isNaN(endedAt.getTime())) return when2;
3715
4130
  const elapsedMs = endedAt.getTime() - startedAt.getTime();
3716
- return `${when} \xB7 ${formatDurationMs(elapsedMs, { style: "long" })}`;
4131
+ return `${when2} \xB7 ${formatDurationMs(elapsedMs, { style: "long" })}`;
3717
4132
  }
3718
4133
  var GUEST_RECORD_ACCESS_LEVELS = [
3719
4134
  "none",
@@ -3789,8 +4204,8 @@ async function loadGuestMeetingRecord(args) {
3789
4204
  );
3790
4205
  }
3791
4206
  if (!response.ok) {
3792
- const refusal = await readRefusal(response);
3793
- return refusedBundle(meeting, refusal.message, refusal.remedy);
4207
+ const refusal2 = await readRefusal(response);
4208
+ return refusedBundle(meeting, refusal2.message, refusal2.remedy);
3794
4209
  }
3795
4210
  const body = await response.json();
3796
4211
  const payload = typeof body["data"] === "object" && body["data"] !== null ? body["data"] : body;
@@ -3881,8 +4296,8 @@ async function setGuestRecordAccess(args) {
3881
4296
  }
3882
4297
  });
3883
4298
  if (!response.ok) {
3884
- const refusal = await readRefusal(response);
3885
- throw new MeetError("forbidden", refusal.message, refusal.remedy);
4299
+ const refusal2 = await readRefusal(response);
4300
+ throw new MeetError("forbidden", refusal2.message, refusal2.remedy);
3886
4301
  }
3887
4302
  const body = await response.json();
3888
4303
  const payload = typeof body["data"] === "object" && body["data"] !== null ? body["data"] : body;
@@ -3932,6 +4347,7 @@ export {
3932
4347
  TRANSCRIPTION_ATTRIBUTES,
3933
4348
  TRANSCRIPTION_TOPIC,
3934
4349
  absenceSentence,
4350
+ announceMeeting,
3935
4351
  applyRoomEvent,
3936
4352
  asCallId,
3937
4353
  asMeetingId,
@@ -3946,6 +4362,7 @@ export {
3946
4362
  canModerateParticipant,
3947
4363
  canTransition,
3948
4364
  captionSegment,
4365
+ changesPatch,
3949
4366
  computeStage,
3950
4367
  countLabel,
3951
4368
  createCallCenter,
@@ -3990,6 +4407,7 @@ export {
3990
4407
  mapQuality,
3991
4408
  meetingInviteAction,
3992
4409
  meetingLink,
4410
+ meetingPath,
3993
4411
  meetingSlug,
3994
4412
  meetingTopic,
3995
4413
  meetingWhen,
@@ -4006,9 +4424,15 @@ export {
4006
4424
  parseRoomToken,
4007
4425
  participantSummary,
4008
4426
  playableRecordingFileId,
4427
+ projectInvitee,
4009
4428
  projectNote,
4429
+ projectOccurrence,
4430
+ projectOverride,
4010
4431
  projectParticipant,
4432
+ projectRsvpInvitation,
4011
4433
  projectSegment,
4434
+ projectUpcoming,
4435
+ pushMeetingToGoogleCalendar,
4012
4436
  readServerRefusal,
4013
4437
  readableTranscript,
4014
4438
  recordingLabel,
@@ -4016,6 +4440,7 @@ export {
4016
4440
  roleOf,
4017
4441
  roomTopic,
4018
4442
  setGuestRecordAccess,
4443
+ settingsPatch,
4019
4444
  snapshotParticipant,
4020
4445
  unavailableFrom,
4021
4446
  userTopic,