@ai-matrx/meet 0.11.41 → 0.11.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,11 +1,29 @@
1
1
  # Changelog — `@ai-matrx/meet`
2
2
 
3
+ ## 0.11.43
4
+
5
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
6
+ `git diff npm/meet/v0.11.42..npm/meet/v0.11.43 -- apps/shared/meet`).
7
+ No source changes intended and no consumer action required.
8
+
9
+ ## 0.11.42
10
+
11
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
12
+ `git diff npm/meet/v0.11.41..npm/meet/v0.11.42 -- apps/shared/meet`).
13
+ No source changes intended and no consumer action required.
14
+
3
15
  ## 0.11.41
4
16
 
5
17
  Automatic changed-only republish (docs/metadata drift since the last tag — see
6
18
  `git diff npm/meet/v0.11.40..npm/meet/v0.11.41 -- apps/shared/meet`).
7
19
  No source changes intended and no consumer action required.
8
20
 
21
+ ## 0.11.40
22
+
23
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
24
+ `git diff npm/meet/v0.11.39..npm/meet/v0.11.40 -- apps/shared/meet`).
25
+ No source changes intended and no consumer action required.
26
+
9
27
  ## 0.11.39
10
28
 
11
29
  Automatic changed-only republish (docs/metadata drift since the last tag — see
@@ -332,7 +332,7 @@ type RecordingState = "idle" | "starting" | "recording" | "stopping" | "processi
332
332
  *
333
333
  * `local` — this browser's own optimistic `starting`/`stopping`, which is all
334
334
  * the indicator ever had through 0.4.7, and the reason it never went solid.
335
- * `room` — a `matrx.meet.recording` packet, the ONLY feed a guest has.
335
+ * `room` — the room's replicated state (metadata), the ONLY feed a guest has.
336
336
  * `record` — the durable `meet_recordings` row. It outranks both (D16).
337
337
  */
338
338
  type RecordingSource = "local" | "room" | "record";
@@ -779,6 +779,10 @@ interface MeetBreakoutPlan {
779
779
  readonly minutes: number;
780
780
  readonly openedAt: string | null;
781
781
  readonly endsAt: string | null;
782
+ /** Set while the host's closing countdown runs: everyone returns to the main meeting at this time. */
783
+ readonly closingAt: string | null;
784
+ /** `invite` (Join / Later) or `auto` (a countdown, then moved) — the org's `meet.breakout_move`. */
785
+ readonly moveMode: "invite" | "auto";
782
786
  readonly closedAt: string | null;
783
787
  readonly broadcasts: readonly MeetBreakoutBroadcast[];
784
788
  }
@@ -861,6 +865,11 @@ declare function assignedRoom(plan: MeetBreakoutPlan | null, identity: string |
861
865
  declare function autoAssign(identities: readonly string[], rooms: number, exclude?: readonly string[]): string[][];
862
866
  /** Milliseconds until the plan's timer ends, or null when it has none. */
863
867
  declare function breakoutRemainingMs(plan: MeetBreakoutPlan | null, now: number): number | null;
868
+ /**
869
+ * The closing countdown to show, in ms — or null when the rooms are not closing. Closing
870
+ * means the host asked (`closingAt`) or the timer is within its last 30 seconds.
871
+ */
872
+ declare function breakoutClosingMs(plan: MeetBreakoutPlan | null, now: number): number | null;
864
873
  /** The language translated captions start in: the knob, else the viewer's own. */
865
874
  declare function defaultCaptionLanguage(settings: MeetCollabSettings | null, viewerLanguage: string | null | undefined): string;
866
875
  /** Languages offered in the captions menu (tags the translator understands). */
@@ -883,4 +892,4 @@ interface MeetingActivities {
883
892
  */
884
893
  declare function loadMeetingActivities(api: MeetApi, meetingId: string, meetingPass?: string | null): Promise<MeetingActivities>;
885
894
 
886
- export { type MeetingOccurrence as $, type AccessTokenSource as A, type MeetPoll as B, type CollabPanel as C, type MeetPollOption as D, EMPTY_COLLAB as E, FILE_SESSION_PATH as F, type MeetPollState as G, type MeetQuestion as H, type InviteeInput as I, type JsonObject as J, type MeetQuestionState as K, type LobbyEntry as L, type MeetingRecord as M, type MeetStroke as N, type OrganizationId as O, type ParticipantIdentity as P, type MeetingActivities as Q, type RoomName as R, type StageLayout as S, type TranscriptSource as T, type UserId as U, type MeetingAttendee as V, type WrapUpAnnouncement as W, type MeetingChanges as X, type MeetingInvitee as Y, type MeetingKind as Z, type MeetingNoteKind as _, type MeetParticipant as a, type MeetingSettings as a0, type NoteTakerState as a1, ORGANIZATION_HEADER as a2, type ObserversVisibleTo as a3, type OccurrenceOverride as a4, type OccurrenceState as a5, type ParticipantStanding as a6, ROLE_RANK as a7, type RecordingPolicy as a8, type RecordingRecord as a9, isObserverRole as aA, loadMeetingActivities as aB, scheduleSettingsPatch as aC, withItem as aD, type RecordingSource as aE, type RecordingState as aa, type RsvpAnswer as ab, type RsvpInvitation as ac, type RsvpState as ad, type TranscriptSegment as ae, type UpcomingOccurrence as af, appPanelOf as ag, applyCollabPacket as ah, asCallId as ai, asMeetingId as aj, asOrganizationId as ak, asParticipantIdentity as al, asRoomName as am, asUserId as an, assignedRoom as ao, autoAssign as ap, breakoutRemainingMs as aq, captureHeldOf as ar, collabFromState as as, createMeetApi as at, defaultCaptionLanguage as au, fileDownloadPath as av, focusFromRow as aw, isGuest as ax, isHostRole as ay, isLinkFollower as az, type MeetApi as b, type MeetIdentity as c, type CallInvite as d, type ParticipantRole as e, type CallMode as f, type MeetingNote as g, type MeetingId as h, type MeetingAppPanel as i, type CollabSnapshot as j, type AdmissionState as k, CAPTION_LANGUAGES as l, type CallId as m, type CallState as n, type CallablePerson as o, type ConnectionQuality as p, type InviteeRole as q, type JsonValue as r, type MeetApiOptions as s, type MeetApiRequest as t, type MeetAppFocus as u, type MeetBreakoutBroadcast as v, type MeetBreakoutPlan as w, type MeetBreakoutRoom as x, type MeetCollabSettings as y, type MeetFileMedia as z };
895
+ export { type MeetingOccurrence as $, type AccessTokenSource as A, type MeetPoll as B, type CollabPanel as C, type MeetPollOption as D, EMPTY_COLLAB as E, FILE_SESSION_PATH as F, type MeetPollState as G, type MeetQuestion as H, type InviteeInput as I, type JsonObject as J, type MeetQuestionState as K, type LobbyEntry as L, type MeetingRecord as M, type MeetStroke as N, type OrganizationId as O, type ParticipantIdentity as P, type MeetingActivities as Q, type RoomName as R, type StageLayout as S, type TranscriptSource as T, type UserId as U, type MeetingAttendee as V, type WrapUpAnnouncement as W, type MeetingChanges as X, type MeetingInvitee as Y, type MeetingKind as Z, type MeetingNoteKind as _, type MeetParticipant as a, type MeetingSettings as a0, type NoteTakerState as a1, ORGANIZATION_HEADER as a2, type ObserversVisibleTo as a3, type OccurrenceOverride as a4, type OccurrenceState as a5, type ParticipantStanding as a6, ROLE_RANK as a7, type RecordingPolicy as a8, type RecordingRecord as a9, isLinkFollower as aA, isObserverRole as aB, loadMeetingActivities as aC, scheduleSettingsPatch as aD, withItem as aE, type RecordingSource as aF, type RecordingState as aa, type RsvpAnswer as ab, type RsvpInvitation as ac, type RsvpState as ad, type TranscriptSegment as ae, type UpcomingOccurrence as af, appPanelOf as ag, applyCollabPacket as ah, asCallId as ai, asMeetingId as aj, asOrganizationId as ak, asParticipantIdentity as al, asRoomName as am, asUserId as an, assignedRoom as ao, autoAssign as ap, breakoutClosingMs as aq, breakoutRemainingMs as ar, captureHeldOf as as, collabFromState as at, createMeetApi as au, defaultCaptionLanguage as av, fileDownloadPath as aw, focusFromRow as ax, isGuest as ay, isHostRole as az, type MeetApi as b, type MeetIdentity as c, type CallInvite as d, type ParticipantRole as e, type CallMode as f, type MeetingNote as g, type MeetingId as h, type MeetingAppPanel as i, type CollabSnapshot as j, type AdmissionState as k, CAPTION_LANGUAGES as l, type CallId as m, type CallState as n, type CallablePerson as o, type ConnectionQuality as p, type InviteeRole as q, type JsonValue as r, type MeetApiOptions as s, type MeetApiRequest as t, type MeetAppFocus as u, type MeetBreakoutBroadcast as v, type MeetBreakoutPlan as w, type MeetBreakoutRoom as x, type MeetCollabSettings as y, type MeetFileMedia as z };
@@ -332,7 +332,7 @@ type RecordingState = "idle" | "starting" | "recording" | "stopping" | "processi
332
332
  *
333
333
  * `local` — this browser's own optimistic `starting`/`stopping`, which is all
334
334
  * the indicator ever had through 0.4.7, and the reason it never went solid.
335
- * `room` — a `matrx.meet.recording` packet, the ONLY feed a guest has.
335
+ * `room` — the room's replicated state (metadata), the ONLY feed a guest has.
336
336
  * `record` — the durable `meet_recordings` row. It outranks both (D16).
337
337
  */
338
338
  type RecordingSource = "local" | "room" | "record";
@@ -779,6 +779,10 @@ interface MeetBreakoutPlan {
779
779
  readonly minutes: number;
780
780
  readonly openedAt: string | null;
781
781
  readonly endsAt: string | null;
782
+ /** Set while the host's closing countdown runs: everyone returns to the main meeting at this time. */
783
+ readonly closingAt: string | null;
784
+ /** `invite` (Join / Later) or `auto` (a countdown, then moved) — the org's `meet.breakout_move`. */
785
+ readonly moveMode: "invite" | "auto";
782
786
  readonly closedAt: string | null;
783
787
  readonly broadcasts: readonly MeetBreakoutBroadcast[];
784
788
  }
@@ -861,6 +865,11 @@ declare function assignedRoom(plan: MeetBreakoutPlan | null, identity: string |
861
865
  declare function autoAssign(identities: readonly string[], rooms: number, exclude?: readonly string[]): string[][];
862
866
  /** Milliseconds until the plan's timer ends, or null when it has none. */
863
867
  declare function breakoutRemainingMs(plan: MeetBreakoutPlan | null, now: number): number | null;
868
+ /**
869
+ * The closing countdown to show, in ms — or null when the rooms are not closing. Closing
870
+ * means the host asked (`closingAt`) or the timer is within its last 30 seconds.
871
+ */
872
+ declare function breakoutClosingMs(plan: MeetBreakoutPlan | null, now: number): number | null;
864
873
  /** The language translated captions start in: the knob, else the viewer's own. */
865
874
  declare function defaultCaptionLanguage(settings: MeetCollabSettings | null, viewerLanguage: string | null | undefined): string;
866
875
  /** Languages offered in the captions menu (tags the translator understands). */
@@ -883,4 +892,4 @@ interface MeetingActivities {
883
892
  */
884
893
  declare function loadMeetingActivities(api: MeetApi, meetingId: string, meetingPass?: string | null): Promise<MeetingActivities>;
885
894
 
886
- export { type MeetingOccurrence as $, type AccessTokenSource as A, type MeetPoll as B, type CollabPanel as C, type MeetPollOption as D, EMPTY_COLLAB as E, FILE_SESSION_PATH as F, type MeetPollState as G, type MeetQuestion as H, type InviteeInput as I, type JsonObject as J, type MeetQuestionState as K, type LobbyEntry as L, type MeetingRecord as M, type MeetStroke as N, type OrganizationId as O, type ParticipantIdentity as P, type MeetingActivities as Q, type RoomName as R, type StageLayout as S, type TranscriptSource as T, type UserId as U, type MeetingAttendee as V, type WrapUpAnnouncement as W, type MeetingChanges as X, type MeetingInvitee as Y, type MeetingKind as Z, type MeetingNoteKind as _, type MeetParticipant as a, type MeetingSettings as a0, type NoteTakerState as a1, ORGANIZATION_HEADER as a2, type ObserversVisibleTo as a3, type OccurrenceOverride as a4, type OccurrenceState as a5, type ParticipantStanding as a6, ROLE_RANK as a7, type RecordingPolicy as a8, type RecordingRecord as a9, isObserverRole as aA, loadMeetingActivities as aB, scheduleSettingsPatch as aC, withItem as aD, type RecordingSource as aE, type RecordingState as aa, type RsvpAnswer as ab, type RsvpInvitation as ac, type RsvpState as ad, type TranscriptSegment as ae, type UpcomingOccurrence as af, appPanelOf as ag, applyCollabPacket as ah, asCallId as ai, asMeetingId as aj, asOrganizationId as ak, asParticipantIdentity as al, asRoomName as am, asUserId as an, assignedRoom as ao, autoAssign as ap, breakoutRemainingMs as aq, captureHeldOf as ar, collabFromState as as, createMeetApi as at, defaultCaptionLanguage as au, fileDownloadPath as av, focusFromRow as aw, isGuest as ax, isHostRole as ay, isLinkFollower as az, type MeetApi as b, type MeetIdentity as c, type CallInvite as d, type ParticipantRole as e, type CallMode as f, type MeetingNote as g, type MeetingId as h, type MeetingAppPanel as i, type CollabSnapshot as j, type AdmissionState as k, CAPTION_LANGUAGES as l, type CallId as m, type CallState as n, type CallablePerson as o, type ConnectionQuality as p, type InviteeRole as q, type JsonValue as r, type MeetApiOptions as s, type MeetApiRequest as t, type MeetAppFocus as u, type MeetBreakoutBroadcast as v, type MeetBreakoutPlan as w, type MeetBreakoutRoom as x, type MeetCollabSettings as y, type MeetFileMedia as z };
895
+ export { type MeetingOccurrence as $, type AccessTokenSource as A, type MeetPoll as B, type CollabPanel as C, type MeetPollOption as D, EMPTY_COLLAB as E, FILE_SESSION_PATH as F, type MeetPollState as G, type MeetQuestion as H, type InviteeInput as I, type JsonObject as J, type MeetQuestionState as K, type LobbyEntry as L, type MeetingRecord as M, type MeetStroke as N, type OrganizationId as O, type ParticipantIdentity as P, type MeetingActivities as Q, type RoomName as R, type StageLayout as S, type TranscriptSource as T, type UserId as U, type MeetingAttendee as V, type WrapUpAnnouncement as W, type MeetingChanges as X, type MeetingInvitee as Y, type MeetingKind as Z, type MeetingNoteKind as _, type MeetParticipant as a, type MeetingSettings as a0, type NoteTakerState as a1, ORGANIZATION_HEADER as a2, type ObserversVisibleTo as a3, type OccurrenceOverride as a4, type OccurrenceState as a5, type ParticipantStanding as a6, ROLE_RANK as a7, type RecordingPolicy as a8, type RecordingRecord as a9, isLinkFollower as aA, isObserverRole as aB, loadMeetingActivities as aC, scheduleSettingsPatch as aD, withItem as aE, type RecordingSource as aF, type RecordingState as aa, type RsvpAnswer as ab, type RsvpInvitation as ac, type RsvpState as ad, type TranscriptSegment as ae, type UpcomingOccurrence as af, appPanelOf as ag, applyCollabPacket as ah, asCallId as ai, asMeetingId as aj, asOrganizationId as ak, asParticipantIdentity as al, asRoomName as am, asUserId as an, assignedRoom as ao, autoAssign as ap, breakoutClosingMs as aq, breakoutRemainingMs as ar, captureHeldOf as as, collabFromState as at, createMeetApi as au, defaultCaptionLanguage as av, fileDownloadPath as aw, focusFromRow as ax, isGuest as ay, isHostRole as az, type MeetApi as b, type MeetIdentity as c, type CallInvite as d, type ParticipantRole as e, type CallMode as f, type MeetingNote as g, type MeetingId as h, type MeetingAppPanel as i, type CollabSnapshot as j, type AdmissionState as k, CAPTION_LANGUAGES as l, type CallId as m, type CallState as n, type CallablePerson as o, type ConnectionQuality as p, type InviteeRole as q, type JsonValue as r, type MeetApiOptions as s, type MeetApiRequest as t, type MeetAppFocus as u, type MeetBreakoutBroadcast as v, type MeetBreakoutPlan as w, type MeetBreakoutRoom as x, type MeetCollabSettings as y, type MeetFileMedia as z };
@@ -1,5 +1,5 @@
1
1
  import { ReactNode } from 'react';
2
- import { M as MeetingRecord, P as ParticipantIdentity, R as RoomName, a as MeetParticipant } from './collab-XY6Xj1dG.cjs';
2
+ import { M as MeetingRecord, P as ParticipantIdentity, R as RoomName, a as MeetParticipant } from './collab-BNJNgXFB.js';
3
3
 
4
4
  /**
5
5
  * THE PRODUCT UI (R6/R7). Not a headless kernel with a note telling the host to
@@ -26,10 +26,12 @@ declare function Avatar({ name, url, size, }: {
26
26
  url?: string | null;
27
27
  size?: number;
28
28
  }): ReactNode;
29
- declare function ControlButton({ label, active, danger, onClick, children, }: {
29
+ declare function ControlButton({ label, active, danger, pressed, onClick, children, }: {
30
30
  label: string;
31
31
  active?: boolean;
32
32
  danger?: boolean;
33
+ /** A toggle's state, for assistive tech and the harness (`aria-pressed`). */
34
+ pressed?: boolean | undefined;
33
35
  onClick: () => void;
34
36
  children: ReactNode;
35
37
  }): ReactNode;
@@ -1,5 +1,5 @@
1
1
  import { ReactNode } from 'react';
2
- import { M as MeetingRecord, P as ParticipantIdentity, R as RoomName, a as MeetParticipant } from './collab-XY6Xj1dG.js';
2
+ import { M as MeetingRecord, P as ParticipantIdentity, R as RoomName, a as MeetParticipant } from './collab-BNJNgXFB.cjs';
3
3
 
4
4
  /**
5
5
  * THE PRODUCT UI (R6/R7). Not a headless kernel with a note telling the host to
@@ -26,10 +26,12 @@ declare function Avatar({ name, url, size, }: {
26
26
  url?: string | null;
27
27
  size?: number;
28
28
  }): ReactNode;
29
- declare function ControlButton({ label, active, danger, onClick, children, }: {
29
+ declare function ControlButton({ label, active, danger, pressed, onClick, children, }: {
30
30
  label: string;
31
31
  active?: boolean;
32
32
  danger?: boolean;
33
+ /** A toggle's state, for assistive tech and the harness (`aria-pressed`). */
34
+ pressed?: boolean | undefined;
33
35
  onClick: () => void;
34
36
  children: ReactNode;
35
37
  }): ReactNode;
package/dist/core.cjs CHANGED
@@ -110,6 +110,7 @@ __export(core_exports, {
110
110
  autoAssign: () => autoAssign,
111
111
  autoRejoinDelayMs: () => autoRejoinDelayMs,
112
112
  availableActivities: () => availableActivities,
113
+ breakoutClosingMs: () => breakoutClosingMs,
113
114
  breakoutRemainingMs: () => breakoutRemainingMs,
114
115
  callInviteAction: () => callInviteAction,
115
116
  callInviteText: () => callInviteText,
@@ -911,6 +912,8 @@ var MEET_ROUTES = {
911
912
  noteTakerStop: "/api/v1/meet/agent/stop",
912
913
  /** In-meeting Q&A. Streams NDJSON; body is `{meeting_id, question}` only. */
913
914
  intelligenceAsk: "/api/v1/meet/intelligence/ask",
915
+ /** Anyone in the meeting (guests with the room pass included) asks for captions for themselves. */
916
+ noteTakerCaptions: "/api/v1/meet/agent/captions",
914
917
  /** A host ends the meeting for everyone. Served by MRI-A6. */
915
918
  meetingEnd: "/api/v1/meet/end",
916
919
  /**
@@ -1057,7 +1060,8 @@ function parseRoomToken(body, roomName) {
1057
1060
  breakoutRoomId: readString(payload, "breakout_room_id") ?? null,
1058
1061
  observersVisibleTo: readString(payload, "observers_visible_to") === "hosts" ? "hosts" : "everyone",
1059
1062
  connectionPolicy: connectionPolicyFrom(payload["connection_policy"]),
1060
- leavePolicy: leavePolicyFrom(payload["leave_policy"])
1063
+ leavePolicy: leavePolicyFrom(payload["leave_policy"]),
1064
+ canReclaim: payload["can_reclaim"] === true
1061
1065
  };
1062
1066
  }
1063
1067
  function createTokenClient(options) {
@@ -1519,6 +1523,42 @@ function createMeetAi(options) {
1519
1523
  },
1520
1524
  "meet.ai.startNoteTaker"
1521
1525
  ),
1526
+ requestCaptions: async ({ meetingId }) => {
1527
+ const roomToken = options.resolveRoomToken?.() ?? null;
1528
+ const operation = "meet.ai.requestCaptions";
1529
+ try {
1530
+ const response = await send({
1531
+ path: MEET_ROUTES.noteTakerCaptions,
1532
+ body: { meeting_id: meetingId },
1533
+ operation,
1534
+ requireAuth: roomToken === null,
1535
+ ...roomToken !== null ? { headers: { [MEETING_PASS_HEADER]: roomToken } } : {}
1536
+ });
1537
+ if (!response.ok) {
1538
+ options.store.patch({ captionsRefusal: await refusalFor(response, operation) });
1539
+ return;
1540
+ }
1541
+ let payload = null;
1542
+ try {
1543
+ payload = await response.json();
1544
+ } catch {
1545
+ payload = null;
1546
+ }
1547
+ const row = typeof payload === "object" && payload !== null ? payload : {};
1548
+ const detail = typeof row["detail"] === "string" ? row["detail"] : "";
1549
+ options.store.patch({
1550
+ captionsRefusal: row["dispatched"] === true ? null : { status: 200, reason: detail, remedy: "" }
1551
+ });
1552
+ } catch (error) {
1553
+ options.store.patch({
1554
+ captionsRefusal: {
1555
+ status: 0,
1556
+ reason: error.message,
1557
+ remedy: error.remedy ?? ""
1558
+ }
1559
+ });
1560
+ }
1561
+ },
1522
1562
  stopNoteTaker: ({ meetingId }) => dispatch(
1523
1563
  MEET_ROUTES.noteTakerStop,
1524
1564
  { meeting_id: meetingId },
@@ -1557,7 +1597,7 @@ function exactNoteTakerParticipant(meeting, participants) {
1557
1597
  }
1558
1598
  function noteTakerStatus(args) {
1559
1599
  const present = exactNoteTakerParticipant(args.meeting, args.participants) !== null;
1560
- const raw = args.meeting?.metadata[NOTE_TAKER_METADATA_KEY];
1600
+ const raw = args.meeting?.metadata?.[NOTE_TAKER_METADATA_KEY];
1561
1601
  const row = typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
1562
1602
  const recorded = readState(row["state"]);
1563
1603
  const detail = typeof row["reason"] === "string" && row["reason"].length > 0 ? row["reason"] : null;
@@ -3290,6 +3330,8 @@ function planFromRow(row) {
3290
3330
  minutes: num2(row["minutes"]) ?? 0,
3291
3331
  openedAt: str(row["opened_at"]),
3292
3332
  endsAt: str(row["ends_at"]),
3333
+ closingAt: str(row["closing_at"]),
3334
+ moveMode: row["move_mode"] === "auto" ? "auto" : "invite",
3293
3335
  closedAt: str(row["closed_at"]),
3294
3336
  broadcasts: (Array.isArray(row["broadcasts"]) ? row["broadcasts"] : []).map(broadcastFrom).filter((b) => b !== null)
3295
3337
  };
@@ -3441,9 +3483,17 @@ function autoAssign(identities, rooms, exclude = []) {
3441
3483
  return out;
3442
3484
  }
3443
3485
  function breakoutRemainingMs(plan, now) {
3444
- if (plan === null || plan.state !== "open" || plan.endsAt === null) return null;
3445
- const end = Date.parse(plan.endsAt);
3446
- return Number.isNaN(end) ? null : Math.max(0, end - now);
3486
+ if (plan === null || plan.state !== "open") return null;
3487
+ const ends = [plan.endsAt, plan.closingAt].map((stamp) => stamp === null ? Number.NaN : Date.parse(stamp)).filter((time) => !Number.isNaN(time));
3488
+ if (ends.length === 0) return null;
3489
+ return Math.max(0, Math.min(...ends) - now);
3490
+ }
3491
+ var BREAKOUT_COUNTDOWN_MS = 3e4;
3492
+ function breakoutClosingMs(plan, now) {
3493
+ if (plan === null || plan.state !== "open") return null;
3494
+ const remaining = breakoutRemainingMs(plan, now);
3495
+ if (remaining === null) return null;
3496
+ return plan.closingAt !== null || remaining <= BREAKOUT_COUNTDOWN_MS ? remaining : null;
3447
3497
  }
3448
3498
  function defaultCaptionLanguage(settings, viewerLanguage) {
3449
3499
  const configured = settings?.captionTranslationLanguage.trim() ?? "";
@@ -3690,6 +3740,47 @@ function recordingLabel(state) {
3690
3740
  }
3691
3741
  }
3692
3742
 
3743
+ // src/core/live-policy.ts
3744
+ var DEFAULT_LIVE_POLICY = {
3745
+ chatMode: "everyone",
3746
+ avLock: "off",
3747
+ timeLimitMinutes: 0,
3748
+ timeLimitWarningMinutes: 5,
3749
+ recordingConsent: "notice"
3750
+ };
3751
+ function oneOf(value, allowed, fallback) {
3752
+ return typeof value === "string" && allowed.includes(value) ? value : fallback;
3753
+ }
3754
+ function whole(value, fallback) {
3755
+ const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
3756
+ return Number.isFinite(n) && n >= 0 ? Math.floor(n) : fallback;
3757
+ }
3758
+ function livePolicyFrom(raw) {
3759
+ const o = typeof raw === "object" && raw !== null ? raw : {};
3760
+ return {
3761
+ chatMode: oneOf(o["chat_mode"], ["everyone", "hosts_only", "off"], DEFAULT_LIVE_POLICY.chatMode),
3762
+ avLock: oneOf(o["attendee_av_lock"], ["off", "mic", "camera", "both"], DEFAULT_LIVE_POLICY.avLock),
3763
+ timeLimitMinutes: whole(o["time_limit_minutes"], 0),
3764
+ timeLimitWarningMinutes: whole(o["time_limit_warning_minutes"], DEFAULT_LIVE_POLICY.timeLimitWarningMinutes),
3765
+ recordingConsent: oneOf(
3766
+ o["recording_consent"],
3767
+ ["notice", "ok_or_leave", "view_only_until_agree"],
3768
+ DEFAULT_LIVE_POLICY.recordingConsent
3769
+ )
3770
+ };
3771
+ }
3772
+ function samePolicy(a, b) {
3773
+ return a.chatMode === b.chatMode && a.avLock === b.avLock && a.timeLimitMinutes === b.timeLimitMinutes && a.timeLimitWarningMinutes === b.timeLimitWarningMinutes && a.recordingConsent === b.recordingConsent;
3774
+ }
3775
+ var MODERATORS = /* @__PURE__ */ new Set(["host", "cohost"]);
3776
+ function isLockedFor(policy, role, kind) {
3777
+ if (MODERATORS.has(role)) return false;
3778
+ return policy.avLock === "both" || (kind === "microphone" ? policy.avLock === "mic" : policy.avLock === "camera");
3779
+ }
3780
+ function consentPending(policy, role, recordingOn, given) {
3781
+ return recordingOn && !given && policy.recordingConsent !== "notice" && !MODERATORS.has(role);
3782
+ }
3783
+
3693
3784
  // src/core/attendance.ts
3694
3785
  var IDLE_MEDIA_REGION = {
3695
3786
  permission: { microphone: "unknown", camera: "unknown" },
@@ -3760,6 +3851,11 @@ var EMPTY = {
3760
3851
  presenter: null,
3761
3852
  waitingCount: 0,
3762
3853
  leavePolicy: DEFAULT_LEAVE_POLICY,
3854
+ livePolicy: DEFAULT_LIVE_POLICY,
3855
+ canReclaim: false,
3856
+ recordingConsentGiven: false,
3857
+ sessionStartedAt: null,
3858
+ mutedByHost: false,
3763
3859
  moderationNotice: null,
3764
3860
  actionNotice: null,
3765
3861
  privateChatRequest: null,
@@ -3769,6 +3865,8 @@ var EMPTY = {
3769
3865
  names: {},
3770
3866
  wrapUp: null,
3771
3867
  noteTakerRefusal: null,
3868
+ captionsOn: false,
3869
+ captionsRefusal: null,
3772
3870
  media: IDLE_MEDIA,
3773
3871
  mediaRegion: IDLE_MEDIA_REGION,
3774
3872
  failure: null,
@@ -3973,7 +4071,12 @@ function createMeetStore(initial = {}) {
3973
4071
  if (current.enabled === next.enabled && current.unavailable?.code === next.unavailable?.code && current.unavailable?.reason === next.unavailable?.reason && current.unavailable?.message === next.unavailable?.message) {
3974
4072
  return;
3975
4073
  }
3976
- commit({ ...state, media: { ...state.media, [kind]: next } });
4074
+ commit({
4075
+ ...state,
4076
+ media: { ...state.media, [kind]: next },
4077
+ // The person turned their own microphone on again: the host's mute is over.
4078
+ ...kind === "microphone" && next.enabled && state.mutedByHost ? { mutedByHost: false } : {}
4079
+ });
3977
4080
  },
3978
4081
  reset() {
3979
4082
  const phase = nextPhase(state.phase, { type: "reset", teardown: true }) ?? EMPTY.phase;
@@ -4011,8 +4114,7 @@ var ROOM_EVENTS = {
4011
4114
  /**
4012
4115
  * A host or co-host acted (Meet wave 2). Payload `{ meeting_id, kind, … }`:
4013
4116
  * `muted {source, by}` · `ask-unmute {by}` · `removed {by}` — addressed to the
4014
- * person concerned; `lower-hands {identities: [...] | "*"}` · `role {identity,
4015
- * role}` — to everyone.
4117
+ * person concerned; `role {identity, role}` — to everyone.
4016
4118
  */
4017
4119
  moderation: "matrx.meet.moderation",
4018
4120
  /**
@@ -4200,10 +4302,11 @@ function moderationText(kind, payload) {
4200
4302
  switch (kind) {
4201
4303
  case "muted": {
4202
4304
  const source = text2(payload["source"]) ?? "microphone";
4203
- return source === "microphone" ? `${by} muted you.` : source === "camera" ? `${by} turned off your camera.` : `${by} stopped your screen share.`;
4305
+ const who = payload["by_role"] === "host" || payload["by_role"] === "cohost" ? "the host" : by;
4306
+ return source === "microphone" ? `You've been muted by ${who}.` : source === "camera" ? `Your camera was turned off by ${who}.` : `Your screen share was stopped by ${who}.`;
4204
4307
  }
4205
4308
  case "ask-unmute":
4206
- return `${by} is asking you to unmute.`;
4309
+ return `${by === "The host" ? "The host" : by} asked you to unmute.`;
4207
4310
  case "removed":
4208
4311
  return `${by} removed you from this meeting.`;
4209
4312
  default:
@@ -4229,7 +4332,7 @@ function applyModeration(store, payload, now) {
4229
4332
  grants: { ...snapshot.grants, role },
4230
4333
  moderationNotice: {
4231
4334
  kind: "role",
4232
- text: role === "host" ? "You are now the host." : role === "cohost" ? "You are now a co-host." : "You are no longer a co-host.",
4335
+ text: role === "host" ? "You are now the host." : role === "cohost" ? "You're now a co-host." : role === "presenter" ? "You're now a presenter." : snapshot.grants.role === "presenter" ? "You're now a participant: you can no longer present." : "You're now a participant: you are no longer a co-host.",
4233
4336
  at: now()
4234
4337
  }
4235
4338
  });
@@ -4241,7 +4344,10 @@ function applyModeration(store, payload, now) {
4241
4344
  case "removed": {
4242
4345
  const sentence = moderationText(kind, payload);
4243
4346
  if (sentence === null) return ignored("a moderation event with no sentence");
4244
- store.patch({ moderationNotice: { kind, text: sentence, at: now() } });
4347
+ store.patch({
4348
+ moderationNotice: { kind, text: sentence, at: now() },
4349
+ ...kind === "muted" && (text2(payload["source"]) ?? "microphone") === "microphone" ? { mutedByHost: true } : {}
4350
+ });
4245
4351
  return HANDLED;
4246
4352
  }
4247
4353
  default:
@@ -5063,6 +5169,7 @@ function applyDoorRow(store, attendee) {
5063
5169
  function decidedState(state) {
5064
5170
  return WAITING_ADMISSION_STATES.includes(state) ? "expired" : state;
5065
5171
  }
5172
+ var LIVE_POLICY_POLL_MS = 3e3;
5066
5173
  function createMeetingSession(options) {
5067
5174
  const { manager, repository, store, identity } = options;
5068
5175
  let channel = null;
@@ -5204,6 +5311,46 @@ function createMeetingSession(options) {
5204
5311
  stopWaiting();
5205
5312
  }
5206
5313
  }
5314
+ let policyTimer = null;
5315
+ let policyReading = false;
5316
+ async function readLivePolicy() {
5317
+ const meeting = store.snapshot().meeting;
5318
+ const pass = options.resolvePass?.() ?? null;
5319
+ if (meeting === null || pass === null || policyReading) return;
5320
+ policyReading = true;
5321
+ try {
5322
+ const response = await options.api.request({
5323
+ path: `${MEET_ROUTES.state}?meeting_id=${encodeURIComponent(meeting.id)}`,
5324
+ method: "GET",
5325
+ requireAuth: false,
5326
+ operation: "meet.state(policy)",
5327
+ headers: { [MEETING_PASS_HEADER]: pass }
5328
+ });
5329
+ if (!response.ok) return;
5330
+ const read = await response.json();
5331
+ const next = livePolicyFrom(read.live_policy);
5332
+ const started = read.session_started_at ? Date.parse(read.session_started_at) : NaN;
5333
+ const snapshot = store.snapshot();
5334
+ const startedAt = Number.isFinite(started) ? started : snapshot.sessionStartedAt;
5335
+ if (!samePolicy(snapshot.livePolicy, next) || startedAt !== snapshot.sessionStartedAt) {
5336
+ store.patch({ livePolicy: next, sessionStartedAt: startedAt });
5337
+ }
5338
+ } catch {
5339
+ } finally {
5340
+ policyReading = false;
5341
+ }
5342
+ }
5343
+ function syncLivePolicyTimer() {
5344
+ const inCall = store.snapshot().phase === "in_call";
5345
+ if (inCall && policyTimer === null) {
5346
+ void readLivePolicy();
5347
+ policyTimer = setInterval(() => void readLivePolicy(), LIVE_POLICY_POLL_MS);
5348
+ } else if (!inCall && policyTimer !== null) {
5349
+ clearInterval(policyTimer);
5350
+ policyTimer = null;
5351
+ }
5352
+ }
5353
+ const unsubscribePolicy = store.subscribe(syncLivePolicyTimer);
5207
5354
  async function readState2() {
5208
5355
  if (reading !== null) return reading;
5209
5356
  const meeting = store.snapshot().meeting;
@@ -5590,6 +5737,9 @@ function createMeetingSession(options) {
5590
5737
  },
5591
5738
  dispose() {
5592
5739
  stopWaiting();
5740
+ unsubscribePolicy();
5741
+ if (policyTimer !== null) clearInterval(policyTimer);
5742
+ policyTimer = null;
5593
5743
  channel?.close();
5594
5744
  channel = null;
5595
5745
  meetingChannel?.close();
@@ -6907,6 +7057,7 @@ function createRoomEngine(options) {
6907
7057
  lastServerUrl = token.serverUrl;
6908
7058
  connectionPolicy = token.connectionPolicy ?? DEFAULT_CONNECTION_POLICY;
6909
7059
  if (token.leavePolicy !== void 0) store.patch({ leavePolicy: token.leavePolicy });
7060
+ store.patch({ canReclaim: token.canReclaim === true });
6910
7061
  store.patch({
6911
7062
  localIdentity: token.identity,
6912
7063
  breakoutRoomId: token.breakoutRoomId ?? null,
@@ -7401,13 +7552,17 @@ var MODERATION_ACTIONS = [
7401
7552
  "ask_unmute",
7402
7553
  "ask_all_unmute",
7403
7554
  "spotlight",
7404
- "present",
7555
+ "sync_presenter",
7405
7556
  "raise_hand",
7406
7557
  "lower_hand",
7407
7558
  "lower_all_hands",
7408
7559
  "make_cohost",
7560
+ "make_presenter",
7561
+ "revoke_presenter",
7562
+ "set_av_lock",
7409
7563
  "revoke_cohost",
7410
7564
  "transfer_host",
7565
+ "reclaim_host",
7411
7566
  "rename",
7412
7567
  "remove",
7413
7568
  "report"
@@ -7433,18 +7588,17 @@ function createInRoomClient(options) {
7433
7588
  function refusal2(response, operation, remedy) {
7434
7589
  return refusalFromResponse(response, operation, remedy);
7435
7590
  }
7436
- let toldPresenting = false;
7591
+ let toldSharing = false;
7437
7592
  let tellChain = Promise.resolve();
7438
7593
  store.subscribe(() => {
7439
7594
  const local = store.snapshot().participants.find((p) => p.isLocal);
7440
7595
  const sharing = local?.screenSharing === true;
7441
- if (sharing === toldPresenting || local === void 0) return;
7442
- toldPresenting = sharing;
7443
- const on = sharing;
7444
- tellChain = tellChain.then(() => client.moderate({ action: "present", on })).then(
7596
+ if (sharing === toldSharing || local === void 0) return;
7597
+ toldSharing = sharing;
7598
+ tellChain = tellChain.then(() => client.moderate({ action: "sync_presenter" })).then(
7445
7599
  () => void 0,
7446
7600
  (error) => {
7447
- console.warn(`[meet] the server did not take presenter=${String(on)}: ${String(error)}`);
7601
+ console.warn(`[meet] the server did not take the screen-share change: ${String(error)}`);
7448
7602
  }
7449
7603
  );
7450
7604
  });
@@ -7461,8 +7615,7 @@ function createInRoomClient(options) {
7461
7615
  action: request.action,
7462
7616
  ...request.identity != null ? { identity: request.identity } : {},
7463
7617
  ...request.name !== void 0 ? { name: request.name } : {},
7464
- ...request.reason !== void 0 ? { reason: request.reason } : {},
7465
- ...request.on !== void 0 ? { on: request.on } : {}
7618
+ ...request.reason !== void 0 ? { reason: request.reason } : {}
7466
7619
  },
7467
7620
  ...hdrs !== void 0 ? { headers: hdrs } : {}
7468
7621
  });
@@ -8339,11 +8492,13 @@ function takeMoveRequest(store) {
8339
8492
 
8340
8493
  // src/core/observation.ts
8341
8494
  function observedRole(role, standing) {
8342
- if (role === "host" || role === "cohost") return role;
8495
+ if (role === "host" || role === "cohost" || role === "presenter") return role;
8343
8496
  return isLinkFollower(standing) ? "guest" : "participant";
8344
8497
  }
8345
- function observedDevice(track, permission = "unknown") {
8498
+ function observedDevice(track, permission = "unknown", held = null) {
8499
+ if (held === "locked") return "locked";
8346
8500
  if (track.enabled) return "on";
8501
+ if (held === "muted_by_host") return "muted_by_host";
8347
8502
  const reason = deviceUnavailableReason(track, permission);
8348
8503
  return reason === null ? "muted_by_me" : `unavailable:${reason}`;
8349
8504
  }
@@ -8380,10 +8535,20 @@ function observe(input) {
8380
8535
  recording: snapshot?.recording === "recording" || snapshot?.recording === "stopping",
8381
8536
  // S6c: the autoplay region is known only inside the room (no remote audio exists elsewhere).
8382
8537
  audioBlocked: inCall && snapshot !== null ? snapshot.mediaRegion.audioOutput === "blocked" : null,
8383
- camera: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.camera, snapshot.mediaRegion.permission.camera) : null,
8384
- microphone: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.microphone, snapshot.mediaRegion.permission.microphone) : null
8538
+ camera: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.camera, snapshot.mediaRegion.permission.camera, lockedFor(snapshot, "camera", inCall)) : null,
8539
+ microphone: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.microphone, snapshot.mediaRegion.permission.microphone, lockedFor(snapshot, "microphone", inCall)) : null
8385
8540
  };
8386
8541
  }
8542
+ function lockedFor(snapshot, kind, inCall) {
8543
+ if (!inCall) return null;
8544
+ const role = effectiveRole(snapshot);
8545
+ if (isLockedFor(snapshot.livePolicy, role, kind)) return "locked";
8546
+ if (snapshot.livePolicy.recordingConsent === "view_only_until_agree" && consentPending(snapshot.livePolicy, role, snapshot.recording === "recording", snapshot.recordingConsentGiven)) {
8547
+ return "locked";
8548
+ }
8549
+ if (kind === "microphone" && snapshot.mutedByHost) return "muted_by_host";
8550
+ return null;
8551
+ }
8387
8552
  function staticObservation(phase, reason = null) {
8388
8553
  return {
8389
8554
  phase,