@ai-matrx/meet 0.11.36 → 0.11.37

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/core.js CHANGED
@@ -556,6 +556,30 @@ function mergeGhosts(live, ghosts, at) {
556
556
  return { roster: [...live, ...extra], kept };
557
557
  }
558
558
 
559
+ // src/core/leave.ts
560
+ var DEFAULT_LEAVE_POLICY = { hostLeavePrompt: "leave_or_end", endConfirm: false };
561
+ var PROMPTS = ["leave_or_end", "must_assign_or_end", "leave_only"];
562
+ function leavePolicyFrom(raw) {
563
+ if (typeof raw !== "object" || raw === null) return DEFAULT_LEAVE_POLICY;
564
+ const value = raw;
565
+ const prompt = value["host_leave_prompt"];
566
+ const confirm = value["end_confirm"];
567
+ return {
568
+ hostLeavePrompt: PROMPTS.includes(prompt) ? prompt : DEFAULT_LEAVE_POLICY.hostLeavePrompt,
569
+ endConfirm: typeof confirm === "boolean" ? confirm : DEFAULT_LEAVE_POLICY.endConfirm
570
+ };
571
+ }
572
+ function leaveChoices(args) {
573
+ if (args.role !== "host") return ["leave"];
574
+ switch (args.policy.hostLeavePrompt) {
575
+ case "must_assign_or_end":
576
+ return args.othersPresent ? ["assign_and_leave", "end_for_all"] : ["leave", "end_for_all"];
577
+ case "leave_only":
578
+ case "leave_or_end":
579
+ return ["leave", "end_for_all"];
580
+ }
581
+ }
582
+
559
583
  // src/core/tokens.ts
560
584
  function createWebRoomTokenStorage(onDiagnostic) {
561
585
  const store = globalThis.localStorage;
@@ -747,7 +771,8 @@ function parseRoomToken(body, roomName) {
747
771
  pollSeconds: typeof payload["poll_seconds"] === "number" ? payload["poll_seconds"] : void 0,
748
772
  breakoutRoomId: readString(payload, "breakout_room_id") ?? null,
749
773
  observersVisibleTo: readString(payload, "observers_visible_to") === "hosts" ? "hosts" : "everyone",
750
- connectionPolicy: connectionPolicyFrom(payload["connection_policy"])
774
+ connectionPolicy: connectionPolicyFrom(payload["connection_policy"]),
775
+ leavePolicy: leavePolicyFrom(payload["leave_policy"])
751
776
  };
752
777
  }
753
778
  function createTokenClient(options) {
@@ -3446,6 +3471,10 @@ var EMPTY = {
3446
3471
  chatHistoryError: null,
3447
3472
  reactions: [],
3448
3473
  spotlight: null,
3474
+ actingHost: null,
3475
+ presenter: null,
3476
+ waitingCount: 0,
3477
+ leavePolicy: DEFAULT_LEAVE_POLICY,
3449
3478
  moderationNotice: null,
3450
3479
  actionNotice: null,
3451
3480
  privateChatRequest: null,
@@ -3507,8 +3536,6 @@ function createMeetStore(initial = {}) {
3507
3536
  },
3508
3537
  setParticipants(participants) {
3509
3538
  const names = learnNames(state.names, participants);
3510
- const hands = new Map(state.participants.map((p) => [p.identity, p.handRaised]));
3511
- const handTimes = new Map(state.participants.map((p) => [p.identity, p.handRaisedAt ?? null]));
3512
3539
  const quality = new Map(state.participants.map((p) => [p.identity, p.quality]));
3513
3540
  commit({
3514
3541
  ...state,
@@ -3521,8 +3548,6 @@ function createMeetStore(initial = {}) {
3521
3548
  // identity could reach a screen, and it is the place that asks the
3522
3549
  // name book. `roster-names.test.ts` fails if a token ever gets past it.
3523
3550
  displayName: displayNameFor(names, participant.identity, participant.displayName),
3524
- handRaised: hands.get(participant.identity) ?? participant.handRaised,
3525
- handRaisedAt: handTimes.get(participant.identity) ?? participant.handRaisedAt ?? null,
3526
3551
  quality: participant.quality === "unknown" ? quality.get(participant.identity) ?? "unknown" : participant.quality
3527
3552
  }))
3528
3553
  });
@@ -3545,20 +3570,6 @@ function createMeetStore(initial = {}) {
3545
3570
  )
3546
3571
  });
3547
3572
  },
3548
- setHand(identity, raised, at) {
3549
- commit({
3550
- ...state,
3551
- participants: state.participants.map(
3552
- (participant) => participant.identity === identity ? {
3553
- ...participant,
3554
- handRaised: raised,
3555
- // A hand that is ALREADY up keeps its place in the queue when
3556
- // it is re-announced (to a late joiner): the first time wins.
3557
- handRaisedAt: raised ? participant.handRaised && participant.handRaisedAt != null ? participant.handRaisedAt : at ?? Date.now() : null
3558
- } : participant
3559
- )
3560
- });
3561
- },
3562
3573
  addChat(message) {
3563
3574
  if (state.chat.some((existing) => existing.id === message.id)) return;
3564
3575
  const chat = [...state.chat, message];
@@ -3707,18 +3718,6 @@ var ROOM_EVENTS = {
3707
3718
  wrapUp: "matrx.meet.wrap_up",
3708
3719
  /** The meeting ended for everyone. Payload `{ meeting_id, ended_at, trigger }`. */
3709
3720
  ended: "matrx.meet.ended",
3710
- /**
3711
- * A `meet_recordings` row changed state. Payload
3712
- * `{ meeting_id, recording: { id, state, started_by } }`.
3713
- *
3714
- * 🚨 THIS IS THE ONLY WAY A GUEST LEARNS A RECORDING IS RUNNING. PreJoin
3715
- * promises every joiner an indicator, and `anon` holds no SELECT grant on
3716
- * `meet_recordings` (MRI-A10 settled that widening it is forbidden), so
3717
- * without this packet the promise was a sentence with nothing behind it: on
3718
- * production 2026-09-09 the guest lane showed no indicator at all while the
3719
- * host recorded (MRI-C8).
3720
- */
3721
- recording: "matrx.meet.recording",
3722
3721
  /**
3723
3722
  * A chat message to everyone was SAVED (Meet wave 2). Payload
3724
3723
  * `{ message: { id, meeting_id, sender_identity, sender_name, body, created_at } }`.
@@ -3771,8 +3770,6 @@ function applyRoomEvent(args) {
3771
3770
  return applyWrapUp(store, payload, now);
3772
3771
  case ROOM_EVENTS.ended:
3773
3772
  return applyEnded(store, payload);
3774
- case ROOM_EVENTS.recording:
3775
- return applyRecording(store, payload);
3776
3773
  case ROOM_EVENTS.chat:
3777
3774
  return applyChat(store, payload);
3778
3775
  case ROOM_EVENTS.moderation:
@@ -3852,9 +3849,9 @@ function applyWrapUp(store, payload, now) {
3852
3849
  store.announceWrapUp(announcement);
3853
3850
  return HANDLED;
3854
3851
  }
3855
- function applyRecording(store, payload) {
3852
+ function applyRecordingState(store, payload) {
3856
3853
  const row = object(payload["recording"]);
3857
- if (row === null) return ignored("a recording event with no `recording` row");
3854
+ if (row === null) return ignored("room state carries no recording");
3858
3855
  const meetingId = text2(payload["meeting_id"]);
3859
3856
  const meeting = store.snapshot().meeting;
3860
3857
  if (meeting !== null && meetingId !== null && meetingId !== meeting.id) {
@@ -3936,19 +3933,6 @@ function applyModeration(store, payload, now) {
3936
3933
  }
3937
3934
  const kind = text2(payload["kind"]);
3938
3935
  switch (kind) {
3939
- case "lower-hands": {
3940
- const raw = payload["identities"];
3941
- const everyone = raw === "*";
3942
- const named = new Set(
3943
- Array.isArray(raw) ? raw.filter((value) => typeof value === "string") : []
3944
- );
3945
- for (const participant of snapshot.participants) {
3946
- if (participant.handRaised && (everyone || named.has(participant.identity))) {
3947
- store.setHand(participant.identity, false);
3948
- }
3949
- }
3950
- return HANDLED;
3951
- }
3952
3936
  case "role": {
3953
3937
  const identity = text2(payload["identity"]);
3954
3938
  const role = text2(payload["role"]);
@@ -4340,7 +4324,6 @@ function gridColumns(count2) {
4340
4324
 
4341
4325
  // src/core/room-driver.ts
4342
4326
  var DATA_TOPICS = {
4343
- hand: "mx.hand",
4344
4327
  reaction: "mx.reaction",
4345
4328
  chat: "mx.chat"
4346
4329
  };
@@ -4974,7 +4957,7 @@ function createMeetingSession(options) {
4974
4957
  })();
4975
4958
  return reading;
4976
4959
  }
4977
- async function postLeave(intent) {
4960
+ async function postLeave(intent, extra = {}) {
4978
4961
  const meeting = requireMeeting(`meet.${intent}`);
4979
4962
  const pass = options.resolvePass?.() ?? null;
4980
4963
  const response = await options.api.request({
@@ -4982,7 +4965,7 @@ function createMeetingSession(options) {
4982
4965
  method: "POST",
4983
4966
  requireAuth: false,
4984
4967
  operation: `meet.${intent}`,
4985
- body: { meeting_id: meeting.id, intent },
4968
+ body: { meeting_id: meeting.id, intent, ...extra },
4986
4969
  ...pass !== null ? { headers: { [MEETING_PASS_HEADER]: pass } } : {}
4987
4970
  });
4988
4971
  if (!response.ok) {
@@ -4996,7 +4979,7 @@ function createMeetingSession(options) {
4996
4979
  throw new MeetError(
4997
4980
  "transport",
4998
4981
  refusal2?.reason ?? `meet.${intent}: the server answered ${response.status}`,
4999
- refusal2?.remedy ?? "You are still in the waiting room. Try again."
4982
+ refusal2?.remedy ?? (intent === "withdraw" ? "You are still in the waiting room. Try again." : "Nothing was changed. Try again.")
5000
4983
  );
5001
4984
  }
5002
4985
  }
@@ -5220,6 +5203,10 @@ function createMeetingSession(options) {
5220
5203
  store.dispatch({ type: "admission", admission: "withdrawn" });
5221
5204
  hint();
5222
5205
  },
5206
+ async hostLeave(intent) {
5207
+ if (intent === "end_for_all") await postLeave("end_for_all");
5208
+ else await postLeave("assign_and_leave", { assign_to: intent.assignTo });
5209
+ },
5223
5210
  async askAgain() {
5224
5211
  await options.rejoin?.();
5225
5212
  },
@@ -6181,6 +6168,11 @@ function SCREEN_SHARE_BLOCKED_REMEDY_FOR(remedy) {
6181
6168
  return /administrator|policy/i.test(remedy) ? remedy : SCREEN_SHARE_BLOCKED_REMEDY;
6182
6169
  }
6183
6170
  var PARTICIPANT_ROLE_ATTRIBUTE = "matrx.participant.role";
6171
+ var PARTICIPANT_HAND_ATTRIBUTE = "matrx.hand";
6172
+ function handFrom(value) {
6173
+ const at = value === void 0 || value.length === 0 ? Number.NaN : Number(value);
6174
+ return Number.isFinite(at) ? { handRaised: true, handRaisedAt: at } : { handRaised: false, handRaisedAt: null };
6175
+ }
6184
6176
  var PARTICIPANT_CAN_RECORD_ATTRIBUTE = "matrx.participant.can_record";
6185
6177
  var CONNECTION_KIND_ATTRIBUTE = "matrx.participant.connection_kind";
6186
6178
  var PARTICIPANT_STANDING_ATTRIBUTE = "matrx.participant.standing";
@@ -6234,7 +6226,10 @@ function projectParticipant(driver, joinedAt) {
6234
6226
  cameraEnabled: driver.cameraEnabled,
6235
6227
  screenSharing: driver.screenSharing,
6236
6228
  speaking: false,
6237
- handRaised: false,
6229
+ // S8: a hand is the SERVER's (`hand_raised_at` on the person row, `/moderate raise_hand`),
6230
+ // replicated as the `matrx.hand` attribute: epoch ms of the raise, "" when down. It is
6231
+ // therefore delivered to late joiners and survives refresh and reconnect.
6232
+ ...handFrom(driver.attributes[PARTICIPANT_HAND_ATTRIBUTE]),
6238
6233
  quality: "unknown",
6239
6234
  joinedAt
6240
6235
  };
@@ -6364,40 +6359,38 @@ function createRoomEngine(options) {
6364
6359
  },
6365
6360
  participants(list) {
6366
6361
  const joinedAt = new Date(now()).toISOString();
6367
- const before = new Set(store.snapshot().participants.map((p) => p.identity));
6368
6362
  projectRoster(list, joinedAt);
6369
- const snapshot = store.snapshot();
6370
- const newcomer = snapshot.participants.some(
6371
- (participant) => !participant.isLocal && !before.has(participant.identity)
6372
- );
6373
- const me = snapshot.participants.find((p) => p.identity === snapshot.localIdentity);
6374
- if (newcomer && before.size > 0 && me?.handRaised === true) {
6375
- void publish(DATA_TOPICS.hand, { raised: true, raisedAt: me.handRaisedAt ?? now() }).catch(
6376
- () => void 0
6377
- );
6378
- }
6379
6363
  },
6380
6364
  participantLeft(who, reason) {
6381
6365
  departures.set(who, reason);
6382
6366
  },
6383
6367
  roomMetadata(metadata) {
6384
- let spotlight = null;
6368
+ let parsed = {};
6385
6369
  if (metadata.length > 0) {
6386
6370
  try {
6387
- const parsed = JSON.parse(metadata);
6388
- if (typeof parsed === "object" && parsed !== null) {
6389
- const value = parsed.spotlight;
6390
- if (typeof value === "string" && value.length > 0) spotlight = asParticipantIdentity(value);
6391
- }
6371
+ const value = JSON.parse(metadata);
6372
+ if (typeof value === "object" && value !== null) parsed = value;
6392
6373
  } catch {
6393
6374
  report(
6394
6375
  "warn",
6395
- "meet: the room's metadata is not JSON, so no spotlight was read from it",
6396
- "Only aidream writes room metadata; check services/meet/moderation.py."
6376
+ "meet: the room's metadata is not JSON, so the room state was not read from it",
6377
+ "Only aidream writes room metadata; check services/meet/room_state.py."
6397
6378
  );
6398
6379
  }
6399
6380
  }
6400
- if (store.snapshot().spotlight !== spotlight) store.patch({ spotlight });
6381
+ const key = (name) => {
6382
+ const value = parsed[name];
6383
+ return typeof value === "string" && value.length > 0 ? asParticipantIdentity(personOf(value)) : null;
6384
+ };
6385
+ const waiting = typeof parsed["waiting_count"] === "number" ? parsed["waiting_count"] : 0;
6386
+ const before = store.snapshot();
6387
+ const next = { spotlight: key("spotlight"), actingHost: key("acting_host"), presenter: key("presenter"), waitingCount: waiting };
6388
+ if (before.spotlight !== next.spotlight || before.actingHost !== next.actingHost || before.presenter !== next.presenter || before.waitingCount !== next.waitingCount) {
6389
+ store.patch(next);
6390
+ }
6391
+ if (parsed["recording"] !== void 0 && parsed["recording"] !== null) {
6392
+ applyRecordingState(store, { meeting_id: parsed["meeting_id"], recording: parsed["recording"] });
6393
+ }
6401
6394
  },
6402
6395
  activeSpeakers(identities) {
6403
6396
  store.setSpeaking([...new Set(identities.map((who) => personOf(who)))]);
@@ -6499,13 +6492,6 @@ function createRoomEngine(options) {
6499
6492
  }
6500
6493
  const sender = personOf(from ?? "");
6501
6494
  switch (topic) {
6502
- case DATA_TOPICS.hand:
6503
- store.setHand(
6504
- sender,
6505
- envelope.raised === true,
6506
- typeof envelope.raisedAt === "number" ? envelope.raisedAt : now()
6507
- );
6508
- return;
6509
6495
  case DATA_TOPICS.reaction:
6510
6496
  if (typeof envelope.emoji === "string") {
6511
6497
  store.addReaction({
@@ -6604,6 +6590,7 @@ function createRoomEngine(options) {
6604
6590
  current = token;
6605
6591
  lastServerUrl = token.serverUrl;
6606
6592
  connectionPolicy = token.connectionPolicy ?? DEFAULT_CONNECTION_POLICY;
6593
+ if (token.leavePolicy !== void 0) store.patch({ leavePolicy: token.leavePolicy });
6607
6594
  store.patch({
6608
6595
  localIdentity: token.identity,
6609
6596
  breakoutRoomId: token.breakoutRoomId ?? null,
@@ -6970,11 +6957,6 @@ function createRoomEngine(options) {
6970
6957
  dismissShareNotice() {
6971
6958
  if (store.snapshot().share.state === "refused") store.patch({ share: NO_SHARE });
6972
6959
  },
6973
- async raiseHand(raised) {
6974
- const local = store.snapshot().localIdentity;
6975
- if (local !== null) store.setHand(local, raised);
6976
- await publish(DATA_TOPICS.hand, { raised, raisedAt: now() });
6977
- },
6978
6960
  async react(emoji) {
6979
6961
  const local = store.snapshot().localIdentity;
6980
6962
  const id = `${local ?? "local"}-${now()}`;
@@ -7103,6 +7085,8 @@ var MODERATION_ACTIONS = [
7103
7085
  "ask_unmute",
7104
7086
  "ask_all_unmute",
7105
7087
  "spotlight",
7088
+ "present",
7089
+ "raise_hand",
7106
7090
  "lower_hand",
7107
7091
  "lower_all_hands",
7108
7092
  "make_cohost",
@@ -7146,7 +7130,8 @@ function createInRoomClient(options) {
7146
7130
  action: request.action,
7147
7131
  ...request.identity != null ? { identity: request.identity } : {},
7148
7132
  ...request.name !== void 0 ? { name: request.name } : {},
7149
- ...request.reason !== void 0 ? { reason: request.reason } : {}
7133
+ ...request.reason !== void 0 ? { reason: request.reason } : {},
7134
+ ...request.on !== void 0 ? { on: request.on } : {}
7150
7135
  },
7151
7136
  ...hdrs !== void 0 ? { headers: hdrs } : {}
7152
7137
  });
@@ -8143,6 +8128,7 @@ export {
8143
8128
  DATA_TOPICS,
8144
8129
  DEFAULT_CONNECTION_POLICY,
8145
8130
  DEFAULT_EFFECTS,
8131
+ DEFAULT_LEAVE_POLICY,
8146
8132
  DEFAULT_PAGE_SIZE,
8147
8133
  EMPTY_COLLAB,
8148
8134
  END_MEETING_NOT_SERVED,
@@ -8177,6 +8163,7 @@ export {
8177
8163
  NO_SHARE,
8178
8164
  ORGANIZATION_HEADER,
8179
8165
  PARTICIPANT_CAN_RECORD_ATTRIBUTE,
8166
+ PARTICIPANT_HAND_ATTRIBUTE,
8180
8167
  PARTICIPANT_ROLE_ATTRIBUTE,
8181
8168
  PARTICIPANT_STANDING_ATTRIBUTE,
8182
8169
  REACTION_TTL_MS,
@@ -8289,6 +8276,8 @@ export {
8289
8276
  isWaitingPhase,
8290
8277
  learnName,
8291
8278
  learnNames,
8279
+ leaveChoices,
8280
+ leavePolicyFrom,
8292
8281
  listenOnlyNotice,
8293
8282
  loadFollowUpDraft,
8294
8283
  loadGuestMeetingRecord,